package dialogue import ( "testing" "time" ) var clarifyNow = time.Date(2026, 7, 31, 10, 0, 0, 0, time.UTC) func TestPendingQuestionAnswerFillsOnlyMissing(t *testing.T) { fireAt := clarifyNow.Add(time.Hour) q := &PendingQuestion{ Intent: IntentReminder, Slots: Slots{Key: "mom", HasKey: true, Text: "напомни позвонить маме"}, Missing: []Slot{SlotTime}, } got := q.Answer("в 11", Slots{Time: fireAt, HasTime: true, Key: "other", HasKey: true}) if !got.HasTime || !got.Time.Equal(fireAt) { t.Fatalf("missing time slot not filled: %+v", got) } if got.Key != "mom" { t.Fatalf("answer overwrote a filled slot: key=%q", got.Key) } if got.Text != "напомни позвонить маме" { t.Fatalf("answer overwrote the original text: %q", got.Text) } } func TestPendingQuestionAnswerKeepsGapWhenAnswerIsEmpty(t *testing.T) { q := &PendingQuestion{Intent: IntentReminder, Missing: []Slot{SlotTime}} got := q.Answer("не знаю", Slots{}) if got.HasTime { t.Fatal("empty answer must not fill the slot") } if len(StillMissing(q.Missing, got)) != 1 { t.Fatal("StillMissing should report the unfilled slot") } } func TestStillMissing(t *testing.T) { cases := []struct { name string want []Slot slots Slots left int }{ {"all filled", []Slot{SlotTime, SlotKey}, Slots{HasTime: true, HasKey: true}, 0}, {"time gap", []Slot{SlotTime}, Slots{HasKey: true}, 1}, {"fn gap", []Slot{SlotFn}, Slots{}, 1}, {"text filled", []Slot{SlotText}, Slots{Text: "hi"}, 0}, {"nothing wanted", nil, Slots{}, 0}, } for _, tc := range cases { if got := StillMissing(tc.want, tc.slots); len(got) != tc.left { t.Errorf("%s: got %v, want %d left", tc.name, got, tc.left) } } } func TestClarifyStoreExpiry(t *testing.T) { s := NewClarifyStore(90 * time.Second) s.Put("voice", &PendingQuestion{Asked: clarifyNow, Missing: []Slot{SlotTime}}) if s.Get("voice", clarifyNow.Add(30*time.Second)) == nil { t.Fatal("question inside the TTL should be live") } if s.Get("voice", clarifyNow.Add(2*time.Minute)) != nil { t.Fatal("question past the TTL should be dropped") } if s.Get("voice", clarifyNow) != nil { t.Fatal("an expired question must be deleted on read, not linger") } } func TestClarifyStoreDefaultTTL(t *testing.T) { s := NewClarifyStore(0) q := &PendingQuestion{Asked: clarifyNow} s.Put("voice", q) if q.TTL != 90*time.Second { t.Fatalf("default TTL not applied: %v", q.TTL) } } func TestCanAskOnce(t *testing.T) { q := &PendingQuestion{} if !q.CanAsk() { t.Fatal("a fresh question should be askable") } q.Attempts = MaxAttempts if q.CanAsk() { t.Fatal("she must not ask twice") } }