package main import ( "context" "testing" "time" "github.com/kami/maven/internal/ipc" "github.com/kami/maven/internal/memory" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/store" "github.com/kami/maven/internal/tool" "github.com/kami/maven/internal/voice" ) func TestCorrectionFragment(t *testing.T) { cases := []struct { utterance string want bool }{ {"нет, не маме, а папе", true}, {"Нет, не маме — а папе", true}, {"no, not mom, but dad", true}, // States something of its own, so it is his to keep. {"нет, я не поеду, а останусь дома", false}, {"нет", false}, {"не маме, а папе", false}, // no refusal word opening it {"нет, маме и папе", false}, // nothing negated {"нет, не маме", false}, // nothing put in its place {"запомни что кофе закончился", false}, } for _, c := range cases { if got := correctionFragment(c.utterance); got != c.want { t.Errorf("correctionFragment(%q) = %v, want %v", c.utterance, got, c.want) } } } func newNoteHandler(t *testing.T) (*reactiveHandler, *store.Store) { t.Helper() st := newTestStore(t) api := ipc.NewStoreAPI(st) now := time.Now() emb := router.NewHashEmbedder(1024) h := &reactiveHandler{ api: api, recall: recallWiring{embedder: emb, memStore: memory.NewInMemoryStore()}, router: buildRouter(emb, tool.NewMatcher(api), 0.55, nil), replier: voice.NewStubReplier(), now: func() time.Time { return now }, dataStore: st, } return h, st } // TestNoteBodyIsTheUtterance — the stored body comes from the utterance, never // from Slots.Text, which the LLM router is free to write anything into (V-576). func TestNoteBodyIsTheUtterance(t *testing.T) { ctx := context.Background() h, st := newNoteHandler(t) dec := router.Decision{ Intent: router.IntentNote, Utterance: "купил хлеб и молоко", Slots: router.Slots{Text: "ты поедешь на дачу"}, } if reply := h.applyAction(ctx, dec); reply != "" { t.Fatalf("applyAction = %q, want empty", reply) } notes, err := st.RecentNotes(ctx, 10) if err != nil { t.Fatalf("RecentNotes: %v", err) } if len(notes) != 1 || notes[0].Text != dec.Utterance { t.Fatalf("stored note = %+v, want body %q", notes, dec.Utterance) } } // TestNoteBodyIsStable — the same utterance twice stores the same text. func TestNoteBodyIsStable(t *testing.T) { ctx := context.Background() h, st := newNoteHandler(t) dec := router.Decision{Intent: router.IntentNote, Utterance: "кофе закончился"} h.applyAction(ctx, dec) h.applyAction(ctx, dec) notes, err := st.RecentNotes(ctx, 10) if err != nil { t.Fatalf("RecentNotes: %v", err) } if len(notes) != 2 { t.Fatalf("notes = %d, want 2", len(notes)) } if notes[0].Text != notes[1].Text || notes[0].Text != dec.Utterance { t.Fatalf("bodies differ: %q vs %q", notes[0].Text, notes[1].Text) } } // TestCorrectionFragmentWritesNoNote — a correction with nothing behind it is // not a note, and she says so instead of filing it (V-576). func TestCorrectionFragmentWritesNoNote(t *testing.T) { ctx := context.Background() h, st := newNoteHandler(t) dec := router.Decision{Intent: router.IntentNote, Utterance: "нет, не маме, а папе"} if reply := h.applyAction(ctx, dec); reply != nothingToCorrectReply { t.Fatalf("reply = %q, want %q", reply, nothingToCorrectReply) } notes, err := st.RecentNotes(ctx, 10) if err != nil { t.Fatalf("RecentNotes: %v", err) } if len(notes) != 0 { t.Fatalf("notes = %+v, want none", notes) } }