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()) } }