package knowledge import ( "context" "errors" "io" "log/slog" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/db" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory/projectindex" ) func testLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } // newTestStore builds a Store backed by an isolated in-memory SQLite project index. func newTestStore(t *testing.T) *Store { t.Helper() ctx := context.Background() d, err := db.Open(ctx, ":memory:", testLogger()) if err != nil { t.Fatalf("open in-memory db: %v", err) } t.Cleanup(func() { _ = d.Close() }) ps, err := projectindex.New(ctx, d, testLogger()) if err != nil { t.Fatalf("projectindex.New: %v", err) } return New(Deps{Projects: ps, Log: testLogger()}) } func TestIndexDocumentPersistsProjectAndChapters(t *testing.T) { t.Parallel() ctx := context.Background() s := newTestStore(t) doc := Doc{Name: "Cyrene 设计", Desc: "v2 设计文档", Content: "这是项目描述段落。\n\n第二段:内部实现要点。\n第三行补充说明。"} if err := s.IndexDocument(ctx, doc); err != nil { t.Fatalf("IndexDocument: %v", err) } docs, err := s.ListDocuments(ctx) if err != nil { t.Fatalf("ListDocuments: %v", err) } if len(docs) != 1 { t.Fatalf("documents = %d, want 1", len(docs)) } if docs[0].Name != "Cyrene 设计" || docs[0].Desc != "v2 设计文档" { t.Fatalf("doc = %+v, want name/desc set", docs[0]) } if docs[0].Chunks != 2 { t.Fatalf("chunks = %d, want 2 (two paragraphs)", docs[0].Chunks) } // The imported content is keyword-searchable via the project index. hits, err := s.Search(ctx, "内部实现") if err != nil { t.Fatalf("Search: %v", err) } if len(hits) == 0 { t.Fatalf("search returned no hits for imported content") } found := false for _, h := range hits { if h.DocName == "Cyrene 设计" && h.Chapter == "第 2 节" { found = true break } } if !found { t.Fatalf("hits = %+v, want chapter 第 2 节 under Cyrene 设计", hits) } } func TestIndexDocumentRejectsBlankNameOrContent(t *testing.T) { t.Parallel() ctx := context.Background() s := newTestStore(t) if err := s.IndexDocument(ctx, Doc{Content: "有内容没名字"}); err == nil { t.Fatal("IndexDocument with blank name should error") } if err := s.IndexDocument(ctx, Doc{Name: "有名字没内容"}); err == nil { t.Fatal("IndexDocument with blank content should error") } } func TestIndexDocumentRequiresProjectStore(t *testing.T) { t.Parallel() ctx := context.Background() s := New(Deps{Log: testLogger()}) // projects == nil err := s.IndexDocument(ctx, Doc{Name: "x", Content: "y"}) if err == nil { t.Fatal("IndexDocument without a project index should error") } } func TestDeleteDocumentStillUnsupported(t *testing.T) { t.Parallel() s := New(Deps{Log: testLogger()}) if err := s.DeleteDocument(context.Background(), "any"); !errors.Is(err, ErrWriteUnsupported) { t.Fatalf("DeleteDocument err = %v, want ErrWriteUnsupported", err) } }