package gateway import ( "context" "encoding/json" "net/http" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/auth" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge" ) // TestAdminRagDeleteRemovesDocument 证明删除写路径是真的:已索引 // 的文档(项目条目 + 其章节)从列表与搜索中消失, // 再次删除它返回 404 而不是编造的成功。 func TestAdminRagDeleteRemovesDocument(t *testing.T) { t.Parallel() ctx := context.Background() rag := newRagStoreForGateway(t) if err := rag.IndexDocument(ctx, knowledge.Doc{ ID: "doc-1", Name: "Cyrene 设计", Desc: "v2 设计文档", Content: "第一段:项目描述。\n\n第二段:内部实现要点。", }); err != nil { t.Fatalf("IndexDocument: %v", err) } srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: rag}) _ = srv tok := signAny(t, jwt, "acct", auth.RoleOwner) resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": "doc-1"}, tok) if resp.StatusCode != http.StatusOK { t.Fatalf("delete status = %d, want 200 (body=%s)", resp.StatusCode, body) } var res struct { ID string `json:"id"` Deleted bool `json:"deleted"` } if err := json.Unmarshal(decodeBody(t, body)["data"], &res); err != nil { t.Fatalf("decode: %v (body=%s)", err, body) } if res.ID != "doc-1" || !res.Deleted { t.Fatalf("response = %+v, want id=doc-1 deleted=true", res) } docs, err := rag.ListDocuments(ctx) if err != nil { t.Fatalf("ListDocuments: %v", err) } if len(docs) != 0 { t.Fatalf("documents = %+v, want empty after delete", docs) } hits, err := rag.Search(ctx, "内部实现") if err != nil { t.Fatalf("Search: %v", err) } if len(hits) != 0 { t.Fatalf("hits = %+v, want none after delete", hits) } resp2, body2 := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": "doc-1"}, tok) if resp2.StatusCode != http.StatusNotFound { t.Fatalf("second delete status = %d, want 404 (body=%s)", resp2.StatusCode, body2) } wantErrorShape(t, body2) } // TestAdminRagDeleteUnwired501 断言未配置的 RAG 服务仍然 // 诚实作答,而不是假装文档已被移除。 func TestAdminRagDeleteUnwired501(t *testing.T) { t.Parallel() srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{}) _ = srv tok := signAny(t, jwt, "acct", auth.RoleOwner) resp, body := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": "doc-1"}, tok) if resp.StatusCode != http.StatusNotImplemented { t.Fatalf("status = %d, want 501 (body=%s)", resp.StatusCode, body) } wantErrorShape(t, body) } // TestAdminRagDeleteRequiresOwner 断言该写路由仍然受 owner 门控。 func TestAdminRagDeleteRequiresOwner(t *testing.T) { t.Parallel() srv, ts, jwt := buildEnv(t, cfgDefault(), Deps{RAG: newRagStoreForGateway(t)}) _ = srv tok := signAny(t, jwt, "acct", auth.RoleAdmin) resp, _ := doReq(t, http.MethodPost, ts.URL+"/api/v1/admin/rag/delete", map[string]any{"id": "doc-1"}, tok) if resp.StatusCode != http.StatusForbidden { t.Fatalf("status = %d, want 403", resp.StatusCode) } }