返回 DeepSeek-Reasonix
forget_test.go
根目录 / internal / memory / forget_test.go
1 package memory
2
3 import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8 )
9
10 // TestForgetToolDeletes drives the tool with raw JSON args and verifies the fact
11 // is removed from the store.
12 func TestForgetToolDeletes(t *testing.T) {
13 store := Store{Dir: t.TempDir()}
14 if _, err := store.Save(Memory{Name: "stale-fact", Description: "d", Type: TypeProject, Body: "b"}); err != nil {
15 t.Fatal(err)
16 }
17
18 tl := NewForgetTool(store)
19 if tl.Name() != "forget" || tl.ReadOnly() {
20 t.Fatalf("unexpected tool identity: name=%q readonly=%v", tl.Name(), tl.ReadOnly())
21 }
22 if !json.Valid(tl.Schema()) {
23 t.Fatal("forget schema is not valid JSON")
24 }
25
26 out, err := tl.Execute(context.Background(), []byte(`{"name":"stale-fact"}`))
27 if err != nil {
28 t.Fatalf("Execute: %v", err)
29 }
30 if !strings.Contains(out, "Forgot memory") || !strings.Contains(out, "archived from project/stale-fact.md") {
31 t.Fatalf("unexpected tool output: %q", out)
32 }
33 if strings.Contains(out, store.Dir) {
34 t.Fatalf("forget output exposed the absolute store path: %q", out)
35 }
36 if len(store.List()) != 0 {
37 t.Fatalf("memory not deleted: %+v", store.List())
38 }
39 }
40
41 func TestForgetToolArchivesOnlyQualifiedScope(t *testing.T) {
42 root := t.TempDir()
43 store := Store{Dir: root + "/project", GlobalDir: root + "/global"}
44 if _, err := store.SaveWithOptions(Memory{Name: "project/shared.md", Description: "project", Body: "project body"}, SaveOptions{}); err != nil {
45 t.Fatal(err)
46 }
47 if _, err := store.SaveWithOptions(Memory{Name: "global/shared.md", Description: "global", Body: "global body"}, SaveOptions{}); err != nil {
48 t.Fatal(err)
49 }
50
51 out, err := NewForgetTool(store).Execute(context.Background(), []byte(`{"name":"global/shared.md"}`))
52 if err != nil {
53 t.Fatal(err)
54 }
55 if !strings.Contains(out, "archived from global/shared.md") || strings.Contains(out, root) {
56 t.Fatalf("qualified forget output = %q", out)
57 }
58 if _, ok := store.Read("global/shared.md"); ok {
59 t.Fatal("global fact remained active")
60 }
61 if project, ok := store.Read("project/shared.md"); !ok || project.Body != "project body" {
62 t.Fatalf("project fact was disturbed: %+v, ok=%v", project, ok)
63 }
64 }
65
66 // TestForgetToolValidates rejects an empty name rather than deleting nothing
67 // silently.
68 func TestForgetToolValidates(t *testing.T) {
69 tl := NewForgetTool(Store{Dir: t.TempDir()})
70 if _, err := tl.Execute(context.Background(), []byte(`{}`)); err == nil {
71 t.Fatal("expected error when name is missing")
72 }
73 }
74
75 // fakeQueue records the turn-tail notes the remember/forget tools queue.
76 type fakeQueue struct{ notes []string }
77
78 func (f *fakeQueue) QueueMemory(note string) { f.notes = append(f.notes, note) }
79
80 // TestForgetToolQueuesDisregardNote verifies a forget injects a turn-tail note so
81 // the model stops trusting the still-cached index line this session.
82 func TestForgetToolQueuesDisregardNote(t *testing.T) {
83 store := Store{Dir: t.TempDir()}
84 if _, err := store.Save(Memory{Name: "old-fact", Description: "d", Type: TypeProject, Body: "b"}); err != nil {
85 t.Fatal(err)
86 }
87 q := &fakeQueue{}
88 ctx := WithQueue(context.Background(), q)
89 if _, err := NewForgetTool(store).Execute(ctx, []byte(`{"name":"old-fact"}`)); err != nil {
90 t.Fatal(err)
91 }
92 if len(q.notes) != 1 || !strings.Contains(q.notes[0], "old-fact") ||
93 !strings.Contains(q.notes[0], "disregard its loaded guidance") {
94 t.Fatalf("expected one queued note revoking the deleted memory, got %v", q.notes)
95 }
96 }
97
97 lines GO