package main import ( "context" "testing" "time" "github.com/kami/maven/internal/ipc" "github.com/kami/maven/internal/router" "github.com/kami/maven/internal/store" ) func TestClassifyAck(t *testing.T) { for _, s := range []string{ "готово", "сделал", "сделано", "выполнил", "уже", "уже сделал", "всё сделал", "done", } { if !classifyAck(s) { t.Errorf("classifyAck(%q) = false, want true", s) } } for _, s := range []string{ // "да" and "ок" belong to the clarify gate, not to the nudge. "да", "ок", "хорошо", // A single-word pattern must not eat the sentence it appears in. "сделал бэкап базы", "готово ли обновление", "уже поздно", "напомни завтра позвонить маме", "", } { if classifyAck(s) { t.Errorf("classifyAck(%q) = true, want false", s) } } } func TestResolveAckMarksTheNudgeActed(t *testing.T) { h, api := snoozeHandler([]ipc.Nudge{pendingNudgeAt(6, 2*time.Minute)}) reply, handled := h.resolveAck(context.Background(), "готово", sourceVoice) if !handled || reply == "" { t.Fatalf("got (%q, %v), want a reply", reply, handled) } if api.gotID != 6 || api.gotOutcome != store.NudgeActed { t.Fatalf("resolved (%d, %q), want (6, %q)", api.gotID, api.gotOutcome, store.NudgeActed) } } func TestResolveAckFallsThroughWithNothingPending(t *testing.T) { h, api := snoozeHandler(nil) if reply, handled := h.resolveAck(context.Background(), "готово", sourceVoice); handled || reply != "" { t.Fatalf("got (%q, %v), want fall-through", reply, handled) } if api.calls != 0 { t.Fatalf("resolved a nudge with nothing pending") } } func TestAckRulesForKey(t *testing.T) { cases := []struct { key string want string // "" means no rule }{ {"water", "water"}, {"meal", "meal"}, {"break", "break"}, {"desk_active", "break"}, {"weight", ""}, {"", ""}, } for _, tc := range cases { got := ackRulesForKey(tc.key) if tc.want == "" { if len(got) != 0 { t.Errorf("ackRulesForKey(%q) = %v, want none", tc.key, got) } continue } if !got[tc.want] { t.Errorf("ackRulesForKey(%q) = %v, want %q in it", tc.key, got, tc.want) } } } func TestAckFromFactClosesTheMatchingNudge(t *testing.T) { h, api := snoozeHandler([]ipc.Nudge{pendingNudgeAt(11, time.Minute)}) // rule "water" h.ackFromFact(context.Background(), router.Decision{ Intent: router.IntentFact, Slots: router.Slots{Key: "water", HasKey: true}, }) if api.gotID != 11 || api.gotOutcome != store.NudgeActed { t.Fatalf("resolved (%d, %q), want (11, %q)", api.gotID, api.gotOutcome, store.NudgeActed) } } func TestAckFromFactIgnoresAnUnrelatedFact(t *testing.T) { // The live nudge is "water"; a meal fact does not answer it. Closing it // anyway would tell the auto-tuner the water rule works when he ignored it. h, api := snoozeHandler([]ipc.Nudge{pendingNudgeAt(12, time.Minute)}) for _, dec := range []router.Decision{ {Intent: router.IntentFact, Slots: router.Slots{Key: "meal", HasKey: true}}, {Intent: router.IntentFact, Slots: router.Slots{Key: "weight", HasKey: true}}, {Intent: router.IntentFact}, // no key {Intent: router.IntentQuery, Slots: router.Slots{Key: "water", HasKey: true}}, {Intent: router.IntentFact, Slots: router.Slots{Key: "water", HasKey: true}, Clarify: true}, } { h.ackFromFact(context.Background(), dec) } if api.calls != 0 { t.Fatalf("resolved %d nudge(s) on unrelated decisions", api.calls) } }