Files
Agent-System/internal/server/chat_attachment_turn_test.go
T
weichen 457e870330 fix(chat): 只上传文档时给出梳理或追问,不再裸报 HTTP 400
空消息加附件可以进入回合。无文字时按解析状态引导或回退审阅,有文字但未处理文档时先回答再追问。4xx 信封不再被前端丢弃。
2026-09-28 02:00:24 +00:00

99 lines
3.3 KiB
Go

package server
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"chat-agent-system/internal/ctxmgr"
)
func TestPrepareChatTurn_AttachmentOnlyAndEmptyRejected(t *testing.T) {
stored, only, reject := prepareChatTurn("", []TurnAttachment{{
Filename: "F-030-PIR.pdf",
AssetID: 9,
ParseState: "pending",
}})
if reject != "" || !only {
t.Fatalf("attachment-only should pass, only=%v reject=%q", only, reject)
}
if !strings.Contains(stored, "F-030-PIR.pdf") {
t.Fatalf("stored message = %q", stored)
}
if _, _, reject = prepareChatTurn(" ", nil); reject != "message cannot be empty" {
t.Fatalf("empty turn reject = %q", reject)
}
stored, only, reject = prepareChatTurn("总结一下", []TurnAttachment{{Filename: "a.pdf"}})
if reject != "" || only || stored != "总结一下" {
t.Fatalf("text+attachment stored=%q only=%v reject=%q", stored, only, reject)
}
}
func TestTurnAttachmentHashKey_ChangesBodyHash(t *testing.T) {
a := turnAttachmentHashKey([]TurnAttachment{{Filename: "a.pdf", AssetID: 1}})
b := turnAttachmentHashKey([]TurnAttachment{{Filename: "a.pdf", AssetID: 2}})
if a == b {
t.Fatal("different assets must not share a hash key")
}
if ctxmgr.HashTurnBodyWithKey("sess", "hello", "") != ctxmgr.HashTurnBody("sess", "hello") {
t.Fatal("empty extra must match the plain body hash")
}
if ctxmgr.HashTurnBodyWithKey("sess", "hello", a) == ctxmgr.HashTurnBodyWithKey("sess", "hello", b) {
t.Fatal("attachment key must change the turn hash")
}
}
func TestChatStream_EmptyMessageWithoutAttachmentRejected(t *testing.T) {
srv := newTestServer(t)
body, _ := json.Marshal(StreamChatRequest{SessionID: "s-empty"})
req := httptest.NewRequest(http.MethodPost, "/api/v1/chat/stream", bytes.NewReader(body))
rec := httptest.NewRecorder()
srv.Routes().ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest || !strings.Contains(rec.Body.String(), "message cannot be empty") {
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
}
}
func TestChatStream_AttachmentOnlyPassesEmptyCheck(t *testing.T) {
srv := newTestServer(t)
body, _ := json.Marshal(StreamChatRequest{
SessionID: "s-upload",
Attachments: []TurnAttachment{{
Filename: "F-030-PIR.pdf",
ParseState: "pending",
}},
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/chat/stream", bytes.NewReader(body))
rec := httptest.NewRecorder()
srv.Routes().ServeHTTP(rec, req)
if rec.Code == http.StatusBadRequest && strings.Contains(rec.Body.String(), "message cannot be empty") {
t.Fatalf("attachment-only must not be rejected as empty: %s", rec.Body.String())
}
}
func TestChat_AttachmentOnlyPendingRepliesInsteadOf400(t *testing.T) {
srv := newTestServer(t)
body, _ := json.Marshal(ChatRequest{
SessionID: "s-upload-sync",
ProjectName: "内窥镜研发项目",
Attachments: []TurnAttachment{{
Filename: "F-030-PIR.pdf",
ParseState: "pending",
}},
})
req := httptest.NewRequest(http.MethodPost, "/api/v1/chat", bytes.NewReader(body))
withBearerAuth(req, loginTestAdmin(t, srv))
rec := httptest.NewRecorder()
srv.Routes().ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "解析") {
t.Fatalf("expected parse guidance, got %s", rec.Body.String())
}
}