package main import ( "context" "strings" "testing" "time" "github.com/kami/maven/internal/router" ) func TestParseRepairReadsTheCorrectedIntent(t *testing.T) { cases := []struct { utterance string want router.Intent ok bool }{ {"нет, ты не поняла, это заметка", router.IntentNote, true}, {"нет, это заметка", router.IntentNote, true}, {"это не напоминание, а заметка", router.IntentNote, true}, {"это заметка, а не напоминание", router.IntentNote, true}, {"ты не так поняла — это факт", router.IntentFact, true}, {"неправильно поняла, это был вопрос", router.IntentQuery, true}, {"you got it wrong, that was a note", router.IntentNote, true}, // No marker: an ordinary request that happens to name an intent. {"запиши заметку купить хлеб", "", false}, {"напомни мне про заметку", "", false}, // A marker with no intent named: nothing to correct to. {"ты не так поняла", "", false}, {"", "", false}, } for _, c := range cases { got, _, ok := parseRepair(c.utterance) if ok != c.ok || (ok && got != c.want) { t.Errorf("parseRepair(%q) = %q,%v; want %q,%v", c.utterance, got, ok, c.want, c.ok) } } } // TestRepairTeachesTheClassifierAndRedoesTheTurn is the whole feature: the // previous utterance is filed under the intent he named, the classifier keeps // it as an example, and he hears that it landed. func TestRepairTeachesTheClassifierAndRedoesTheTurn(t *testing.T) { h, st, now := newClarifyHandler(t) emb := router.NewHashEmbedder(256) cls := router.NewClassifier(emb) h.embedder = emb h.router = router.New(router.Config{Classifier: cls, Extractor: h.extractor}) ctx := context.Background() h.recordTurn("купить хлеб", router.IntentFact) reply, handled := h.resolveRepair(ctx, "нет, ты не поняла, это заметка") if !handled { t.Fatal("a spoken correction was not handled") } if !strings.Contains(reply, "заметка") { t.Errorf("the correction is not named out loud: %q", reply) } if strings.Contains(reply, "не вышло") { t.Errorf("learning failed unexpectedly: %q", reply) } ex := cls.Examples(router.IntentNote) if len(ex) != 1 || ex[0].Text != "купить хлеб" { t.Fatalf("the classifier did not learn the correction: %+v", ex) } notes, err := st.RecentNotes(ctx, 5) if err != nil { t.Fatalf("recent notes: %v", err) } if len(notes) != 1 || !strings.Contains(notes[0].Text, "купить хлеб") { t.Fatalf("the request was not redone as a note: %+v", notes) } _ = now } func TestRepairNeedsARecentTurnToPointAt(t *testing.T) { h, _, now := newClarifyHandler(t) h.router = router.New(router.Config{Classifier: router.NewClassifier(router.NewHashEmbedder(256))}) ctx := context.Background() // Nothing said yet. if _, handled := h.resolveRepair(ctx, "нет, это заметка"); handled { t.Error("a correction with no previous turn was handled") } // Said, but long ago. h.recordTurn("купить хлеб", router.IntentFact) *now = now.Add(repairWindow + time.Minute) if _, handled := h.resolveRepair(ctx, "нет, это заметка"); handled { t.Error("a correction outside the window was handled") } } func TestRepairIsSpentOnce(t *testing.T) { h, _, _ := newClarifyHandler(t) emb := router.NewHashEmbedder(256) h.embedder = emb h.router = router.New(router.Config{Classifier: router.NewClassifier(emb), Extractor: h.extractor}) ctx := context.Background() h.recordTurn("купить хлеб", router.IntentFact) if _, handled := h.resolveRepair(ctx, "нет, это заметка"); !handled { t.Fatal("the first correction was not handled") } if _, handled := h.resolveRepair(ctx, "нет, это заметка"); handled { t.Error("the same turn was corrected twice") } } // TestRepairPassesWhenSheAlreadyDidThat — he names the intent she used. There // is nothing to teach and redoing it would file the request a second time. func TestRepairPassesWhenSheAlreadyDidThat(t *testing.T) { h, _, _ := newClarifyHandler(t) h.router = router.New(router.Config{Classifier: router.NewClassifier(router.NewHashEmbedder(256))}) h.recordTurn("купить хлеб", router.IntentNote) if _, handled := h.resolveRepair(context.Background(), "нет, это заметка"); handled { t.Error("a correction to the intent she already used was handled") } }