Files
ragflow/internal/channels/bootstrap_test.go

180 lines
5.5 KiB
Go

//
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package channels
import (
"context"
"os"
"path/filepath"
"testing"
"time"
"ragflow/internal/channels/core"
)
func TestStartRetryDelay(t *testing.T) {
tests := []struct {
name string
attempts int
want time.Duration
}{
{name: "zero attempt uses first delay", attempts: 0, want: initialStartRetryDelay},
{name: "first attempt", attempts: 1, want: initialStartRetryDelay},
{name: "second attempt doubles", attempts: 2, want: 2 * initialStartRetryDelay},
{name: "fifth attempt", attempts: 5, want: 16 * initialStartRetryDelay},
{name: "bounded", attempts: 20, want: maxStartRetryDelay},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := startRetryDelay(tt.attempts); got != tt.want {
t.Fatalf("startRetryDelay(%d) = %s, want %s", tt.attempts, got, tt.want)
}
})
}
}
func TestRecordStartFailure(t *testing.T) {
rt := NewRuntime()
now := time.Unix(100, 0)
rt.recordStartFailure("account-1", "fp-1", now)
first := rt.failed["account-1"]
if first.attempts != 1 {
t.Fatalf("attempts after first failure = %d, want 1", first.attempts)
}
if !first.nextRetryAt.Equal(now.Add(initialStartRetryDelay)) {
t.Fatalf("nextRetryAt after first failure = %s, want %s", first.nextRetryAt, now.Add(initialStartRetryDelay))
}
rt.recordStartFailure("account-1", "fp-1", now)
second := rt.failed["account-1"]
if second.attempts != 2 {
t.Fatalf("attempts after second failure = %d, want 2", second.attempts)
}
if !second.nextRetryAt.Equal(now.Add(2 * initialStartRetryDelay)) {
t.Fatalf("nextRetryAt after second failure = %s, want %s", second.nextRetryAt, now.Add(2*initialStartRetryDelay))
}
rt.recordStartFailure("account-1", "fp-2", now)
changed := rt.failed["account-1"]
if changed.attempts != 1 {
t.Fatalf("attempts after fingerprint change = %d, want 1", changed.attempts)
}
}
func TestClearStartFailure(t *testing.T) {
rt := NewRuntime()
rt.recordStartFailure("account-1", "fp-1", time.Unix(100, 0))
rt.clearStartFailure("account-1")
if _, ok := rt.failed["account-1"]; ok {
t.Fatal("clearStartFailure left failed entry behind")
}
}
func TestGatewayWorkdirDefaultContainsNodeEntrypoint(t *testing.T) {
t.Setenv("WHATSAPP_GATEWAY_WORKDIR", "")
entry := filepath.Join(gatewayWorkdir(), "index.js")
if _, err := os.Stat(entry); err != nil {
t.Fatalf("default gateway entry %s is not available: %v", entry, err)
}
}
func TestGatewayEnabledDefaultsToUserManaged(t *testing.T) {
t.Setenv("WHATSAPP_GATEWAY_ENABLED", "")
if gatewayEnabled() {
t.Fatal("gatewayEnabled() = true, want false by default")
}
}
func TestGatewayEnabledCanBeExplicitlyEnabled(t *testing.T) {
t.Setenv("WHATSAPP_GATEWAY_ENABLED", "true")
if !gatewayEnabled() {
t.Fatal("gatewayEnabled() = false, want true")
}
}
func TestGatewayEnabledRejectsInvalidValue(t *testing.T) {
t.Setenv("WHATSAPP_GATEWAY_ENABLED", "maybe")
if gatewayEnabled() {
t.Fatal("gatewayEnabled() = true for invalid value, want false")
}
}
func TestWhatsAppEnqueueDoesNotMarkDroppedMessageSeen(t *testing.T) {
ch := newWhatsAppChannel(whatsappAccount{AccountID: "account-1"})
worker := &whatsappChatWorker{queue: make(chan core.IncomingMessage, 1)}
worker.queue <- core.IncomingMessage{MessageID: "queued"}
ch.workers["chat-1"] = worker
ok := ch.enqueueIncoming(context.Background(), core.IncomingMessage{
ChatID: "chat-1",
MessageID: "dropped",
})
if ok {
t.Fatal("enqueueIncoming succeeded for a full queue")
}
if _, seen := ch.seen["dropped"]; seen {
t.Fatal("dropped message was marked seen")
}
}
func TestWhatsAppEnqueueMarksSeenAfterSuccessfulHandoff(t *testing.T) {
ch := newWhatsAppChannel(whatsappAccount{AccountID: "account-1"})
ch.workers["chat-1"] = &whatsappChatWorker{queue: make(chan core.IncomingMessage, 1)}
msg := core.IncomingMessage{ChatID: "chat-1", MessageID: "message-1"}
if ok := ch.enqueueIncoming(context.Background(), msg); !ok {
t.Fatal("enqueueIncoming failed for an empty queue")
}
if _, seen := ch.seen[msg.MessageID]; !seen {
t.Fatal("successfully handed off message was not marked seen")
}
if ok := ch.enqueueIncoming(context.Background(), msg); ok {
t.Fatal("duplicate message was enqueued")
}
}
func TestWhatsAppRetireChatWorkerRequiresCurrentEmptyWorker(t *testing.T) {
ch := newWhatsAppChannel(whatsappAccount{AccountID: "account-1"})
worker := &whatsappChatWorker{queue: make(chan core.IncomingMessage, 1)}
worker.queue <- core.IncomingMessage{ChatID: "chat-1", MessageID: "message-1"}
ch.workers["chat-1"] = worker
if ch.retireChatWorker("chat-1", worker) {
t.Fatal("retireChatWorker retired a non-empty worker")
}
if ch.workers["chat-1"] != worker {
t.Fatal("non-empty worker was removed from ownership map")
}
<-worker.queue
if !ch.retireChatWorker("chat-1", worker) {
t.Fatal("retireChatWorker did not retire an empty current worker")
}
if _, ok := ch.workers["chat-1"]; ok {
t.Fatal("empty retired worker remains in ownership map")
}
}