Files
姚章浩 00c4c13283
build / build (push) Successful in 1m19s
fix(qa): 首批即显并统一五字段后端筛选
列表正文改为按需读取;保留完整导出、版本隔离和有效标签筛选。
验证:前端全量、Go全量及隔离PG全mirror竞态通过;覆盖首批回调、续载失败与筛选分页。
2026-10-25 12:45:31 +08:00

94 lines
3.6 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"knowledge-governance/internal/dobotaiops"
"net/http"
"net/http/httptest"
"strings"
"testing"
"unicode/utf8"
)
type qaPreviewSpy struct {
qaOpsSpy
record dobotaiops.Record
}
func (s *qaPreviewSpy) ListRecords(_ context.Context, q dobotaiops.RecordQuery) (dobotaiops.RecordPage, error) {
s.query = q
return dobotaiops.RecordPage{Items: []dobotaiops.Record{s.record}, Total: 1}, nil
}
func TestQALightweightListPreservesFullRecordAndRejectsUnknownProjection(t *testing.T) {
full := strings.Repeat("长😀", 900)
spy := &qaPreviewSpy{record: dobotaiops.Record{ID: "preview-fixture", Question: full, Answer: full, MasterFields: map[string]string{"question_chinese": full, "product_category": "MG400"}, References: json.RawMessage(`[{"content":"private-reference-body"}]`), Media: json.RawMessage(`[{"url":"private-media"}]`)}}
auth := newFakeAuthStore()
auth.sessions["admin-qa-fixture"] = "usr-boss"
h, err := NewHandler(Config{Store: &fakeJobStore{}, QAOps: spy, QAOpsTenantKey: "approved-tenant", Auth: &AuthConfig{Store: auth}})
if err != nil {
t.Fatal(err)
}
base := "/api/v1/qa-ops/records?start=2026-10-14T00:00:00Z&end=2026-10-15T00:00:00Z"
for _, mode := range []string{"summary", "full", "invalid"} {
req := httptest.NewRequest("GET", base+"&projection="+mode, nil)
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "admin-qa-fixture"})
out := httptest.NewRecorder()
h.ServeHTTP(out, req)
if mode == "invalid" {
if out.Code != 400 {
t.Fatal("unknown projection accepted")
}
continue
}
if out.Code != 200 {
t.Fatalf("projection %s status %d", mode, out.Code)
}
var data struct{ Data dobotaiops.RecordPage }
if json.Unmarshal(out.Body.Bytes(), &data) != nil || len(data.Data.Items) != 1 {
t.Fatal("invalid page")
}
row := data.Data.Items[0]
if mode == "summary" {
if utf8.RuneCountInString(row.Answer) > 401 || row.Answer == full || strings.Contains(out.Body.String(), "private-reference-body") || strings.Contains(out.Body.String(), "private-media") {
t.Fatal("summary leaked full content")
}
} else if row.Answer != full || !strings.Contains(out.Body.String(), "private-reference-body") {
t.Fatal("full export content changed")
}
}
if spy.record.Answer != full || spy.record.MasterFields["question_chinese"] != full {
t.Fatal("preview mutated original")
}
_ = http.StatusOK
}
func TestQARecordsAcceptFiveExactFiltersAndRejectUnsupportedValues(t *testing.T) {
spy := &qaOpsSpy{}
auth := newFakeAuthStore()
auth.sessions["admin-qa-fixture"] = "usr-boss"
h, err := NewHandler(Config{Store: &fakeJobStore{}, QAOps: spy, QAOpsTenantKey: "approved-tenant", Auth: &AuthConfig{Store: auth}})
if err != nil {
t.Fatal(err)
}
base := "/api/v1/qa-ops/records?start=2026-10-14T00:00:00Z&end=2026-10-15T00:00:00Z"
for _, suffix := range []string{"&coverage=是&is_correct=否&product_category=MG400&problem_type=参数&root_cause=知识缺失", "&root_cause=__empty__"} {
req := httptest.NewRequest("GET", base+suffix, nil)
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "admin-qa-fixture"})
out := httptest.NewRecorder()
h.ServeHTTP(out, req)
if out.Code != 200 || len(spy.query.Filters) == 0 {
t.Fatalf("filter not forwarded: %d", out.Code)
}
}
for _, suffix := range []string{"&coverage=invalid", "&is_correct=是&is_correct=否", "&product_category=" + strings.Repeat("x", 501)} {
req := httptest.NewRequest("GET", base+suffix, nil)
req.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "admin-qa-fixture"})
out := httptest.NewRecorder()
h.ServeHTTP(out, req)
if out.Code != 400 {
t.Fatal("invalid filters accepted")
}
}
}