| 1 | package memory |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | // CJK bigram selectivity: a query phrase must match a real two-character |
| 9 | // subsequence, not scattered common characters (the retired per-rune scorer |
| 10 | // matched the latter). |
| 11 | func TestAutoRecallCJKBigramsRequireRealSubsequence(t *testing.T) { |
| 12 | store := recallTestStore(t) |
| 13 | recallTestWrite(t, store.Dir, Memory{ |
| 14 | ID: "mem-migration", Name: "db-migration-compat", Title: "数据库迁移兼容性", |
| 15 | Description: "数据库迁移必须保持向后兼容", Type: TypeProject, |
| 16 | Scope: FactScopeProject, Body: "所有 schema 变更先加列后删列。", |
| 17 | }) |
| 18 | recallTestWrite(t, store.Dir, Memory{ |
| 19 | ID: "mem-scattered", Name: "warehouse-stats", Title: "仓库统计口径", |
| 20 | Description: "数值统计以据点仓库的库存为准", Type: TypeProject, |
| 21 | Scope: FactScopeProject, Body: "库存数字每周核对一次。", |
| 22 | }) |
| 23 | |
| 24 | result := AutoRecall(store, "数据库迁移怎么保证兼容", RecallOptions{}) |
| 25 | if len(result.Hits) == 0 { |
| 26 | t.Fatalf("AutoRecall missed the real 数据库迁移 fact: %+v", result) |
| 27 | } |
| 28 | for _, hit := range result.Hits { |
| 29 | if hit.Memory.ID == "mem-scattered" { |
| 30 | t.Fatalf("scattered common characters must not match: %+v", result.Hits) |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | // Keywords bridge the lexical gap AutoRecall cannot cross: the fact's own |
| 36 | // words never appear in the query, its saved aliases do. |
| 37 | func TestAutoRecallMatchesThroughSavedKeywords(t *testing.T) { |
| 38 | store := recallTestStore(t) |
| 39 | recallTestWrite(t, store.Dir, Memory{ |
| 40 | ID: "mem-pnpm", Name: "package-manager", Title: "Package manager is pnpm", |
| 41 | Description: "The project migrated from npm to pnpm", Type: TypeProject, |
| 42 | Scope: FactScopeProject, Keywords: "依赖 安装 包管理 install dependencies", |
| 43 | Body: "Use pnpm for every workspace task; npm and yarn lockfiles are gone.", |
| 44 | }) |
| 45 | |
| 46 | result := AutoRecall(store, "依赖应该用什么工具安装", RecallOptions{}) |
| 47 | if len(result.Hits) != 1 || result.Hits[0].Memory.ID != "mem-pnpm" { |
| 48 | t.Fatalf("keywords should carry the paraphrased query to the fact, got %+v", result.Hits) |
| 49 | } |
| 50 | if !strings.Contains(result.Hits[0].Reason, "依赖") && !strings.Contains(result.Hits[0].Reason, "安装") { |
| 51 | t.Fatalf("reason should surface the matched alias, got %q", result.Hits[0].Reason) |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | func TestSaveRoundTripsKeywordsAndPreservesThemOnUpdate(t *testing.T) { |
| 56 | store := recallTestStore(t) |
| 57 | saved, err := store.SaveWithOptions(Memory{ |
| 58 | Name: "package-manager", Description: "The project uses pnpm", |
| 59 | Keywords: "依赖 包管理", Body: "pnpm everywhere.", |
| 60 | }, SaveOptions{}) |
| 61 | if err != nil { |
| 62 | t.Fatal(err) |
| 63 | } |
| 64 | loaded, ok := store.Read(saved.Memory.ID) |
| 65 | if !ok || loaded.Keywords != "依赖 包管理" { |
| 66 | t.Fatalf("keywords lost in round trip: ok=%v %+v", ok, loaded) |
| 67 | } |
| 68 | |
| 69 | if _, err := store.SaveWithOptions(Memory{ |
| 70 | ID: saved.Memory.ID, Description: "The project uses pnpm 9", |
| 71 | Body: "pnpm 9 everywhere.", |
| 72 | }, SaveOptions{}); err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | updated, ok := store.Read(saved.Memory.ID) |
| 76 | if !ok || updated.Keywords != "依赖 包管理" { |
| 77 | t.Fatalf("an update omitting keywords must preserve them, got %+v", updated) |
| 78 | } |
| 79 | if updated.Revision != 2 || !strings.Contains(updated.Body, "pnpm 9") { |
| 80 | t.Fatalf("update did not land: %+v", updated) |
| 81 | } |
| 82 | } |
| 83 |