mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-01 21:37:33 +08:00
### Summary As title, this can not be tested for now Close #17520 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
143 lines
4.8 KiB
Go
143 lines
4.8 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"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"ragflow/internal/channels/core"
|
|
)
|
|
|
|
func TestNewLineChannelFromConfigRequiresCredentials(t *testing.T) {
|
|
if _, err := newLineChannelFromConfig("account-1", map[string]any{"channel_secret": "secret"}); err == nil {
|
|
t.Fatal("newLineChannelFromConfig succeeded without channel_access_token")
|
|
}
|
|
}
|
|
|
|
func TestNewLineChannelFromConfigNormalizesConfig(t *testing.T) {
|
|
ch, err := newLineChannelFromConfig("account-1", map[string]any{
|
|
"channel_secret": "secret-1",
|
|
"channel_access_token": "token-1",
|
|
"webhook_host": "127.0.0.1",
|
|
"webhook_port": "3011",
|
|
"timeout_secs": "7",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("newLineChannelFromConfig returned error: %v", err)
|
|
}
|
|
if ch.account.ChannelSecret != "secret-1" {
|
|
t.Fatalf("channel secret = %q, want secret-1", ch.account.ChannelSecret)
|
|
}
|
|
if ch.account.ChannelAccessToken != "token-1" {
|
|
t.Fatalf("channel access token = %q, want token-1", ch.account.ChannelAccessToken)
|
|
}
|
|
if ch.account.WebhookHost != "127.0.0.1" {
|
|
t.Fatalf("webhook host = %q, want 127.0.0.1", ch.account.WebhookHost)
|
|
}
|
|
if ch.account.WebhookPort != 3011 {
|
|
t.Fatalf("webhook port = %d, want 3011", ch.account.WebhookPort)
|
|
}
|
|
if ch.account.Timeout != 7*time.Second {
|
|
t.Fatalf("timeout = %s, want 7s", ch.account.Timeout)
|
|
}
|
|
}
|
|
|
|
func TestLineWebhookRejectsBadSignature(t *testing.T) {
|
|
ch := newLineChannel(lineAccount{AccountID: "account-1", ChannelSecret: "secret", ChannelAccessToken: "token"})
|
|
server := &lineWebhookServer{channels: map[string]*lineChannel{"account-1": ch}}
|
|
req := httptest.NewRequest(http.MethodPost, "/line/account-1/webhook", strings.NewReader(`{"events":[]}`))
|
|
req.Header.Set("X-Line-Signature", "bad")
|
|
rec := httptest.NewRecorder()
|
|
|
|
server.handleRequest(rec, req)
|
|
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusForbidden)
|
|
}
|
|
}
|
|
|
|
func TestLineWebhookDispatchesTextMessage(t *testing.T) {
|
|
body := `{"events":[{"type":"message","replyToken":"reply-1","source":{"type":"user","userId":"user-1"},"message":{"id":"message-1","type":"text","text":"hello"}}]}`
|
|
ch := newLineChannel(lineAccount{AccountID: "account-1", ChannelSecret: "secret", ChannelAccessToken: "token"})
|
|
server := &lineWebhookServer{channels: map[string]*lineChannel{"account-1": ch}}
|
|
|
|
var got core.IncomingMessage
|
|
handled := make(chan struct{}, 1)
|
|
ch.SetMessageHandler(func(ctx context.Context, msg core.IncomingMessage) error {
|
|
got = msg
|
|
handled <- struct{}{}
|
|
return nil
|
|
})
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/line/account-1/webhook", strings.NewReader(body))
|
|
req.Header.Set("X-Line-Signature", lineSignature("secret", body))
|
|
rec := httptest.NewRecorder()
|
|
|
|
server.handleRequest(rec, req)
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
|
}
|
|
select {
|
|
case <-handled:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("message handler was not invoked")
|
|
}
|
|
if got.Channel != "line" || got.AccountID != "account-1" || got.ChatID != "user-1" || got.ChatType != "p2p" || got.MessageID != "message-1" || got.Text != "hello" {
|
|
t.Fatalf("incoming message = %#v", got)
|
|
}
|
|
if token := ch.takeReplyToken("message-1"); token != "reply-1" {
|
|
t.Fatalf("reply token = %q, want reply-1", token)
|
|
}
|
|
if token := ch.takeReplyToken("message-1"); token != "" {
|
|
t.Fatalf("reply token was not single-use: %q", token)
|
|
}
|
|
}
|
|
|
|
func TestLineEnqueueDoesNotMarkDroppedMessageSeen(t *testing.T) {
|
|
ch := newLineChannel(lineAccount{AccountID: "account-1", ChannelSecret: "secret", ChannelAccessToken: "token"})
|
|
worker := &lineWorker{queue: make(chan core.IncomingMessage, 1)}
|
|
worker.queue <- core.IncomingMessage{MessageID: "queued"}
|
|
ch.workers["chat-1"] = worker
|
|
|
|
ok := ch.enqueueIncoming(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 lineSignature(secret, body string) string {
|
|
mac := hmac.New(sha256.New, []byte(secret))
|
|
_, _ = mac.Write([]byte(body))
|
|
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
|
|
}
|