package main import ( "context" "strings" "testing" "github.com/kami/maven/internal/router" ) // TestEntityReferenceText pins when his own words win over the model's. func TestEntityReferenceText(t *testing.T) { for _, tc := range []struct { name string utterance string text string want string }{ { name: "the model transliterated the name", utterance: "перезапусти muzick indexer", text: "перезагрузить музик индексер", want: "muzick indexer", }, { name: "it kept the name, so nothing to repair", utterance: "перезапусти muzick indexer", text: "перезагрузить muzick indexer", want: "перезагрузить muzick indexer", }, { name: "an all-Russian entity name is not a rewrite", utterance: "перезапусти домашний сервер", text: "перезагрузить домашний сервер", want: "перезагрузить домашний сервер", }, { name: "an English turn never enters the recovery", utterance: "restart muzick indexer", text: "restart muzick indexer", want: "restart muzick indexer", }, { name: "the longest Latin run is the name", utterance: "а перезапусти-ка nginx на muzick-indexer, пожалуйста", text: "перезагрузить нгинкс", want: "muzick-indexer", }, { name: "one stray letter is not a name", utterance: "перезапусти сервер a", text: "перезагрузить сервер", want: "перезагрузить сервер", }, } { t.Run(tc.name, func(t *testing.T) { dec := router.Decision{Utterance: tc.utterance, Slots: router.Slots{Text: tc.text}} if got := entityReferenceText(dec); got != tc.want { t.Fatalf("entityReferenceText = %q, want %q", got, tc.want) } }) } } // TestNexusIsAskedForTheNameHeSaid — the defect end to end (Vikunja #476): the // router hands over a transliterated Text, and Nexus must still be asked about // the service that exists. func TestNexusIsAskedForTheNameHeSaid(t *testing.T) { ctx := context.Background() nexus := newFakeNexus(t, fixtureNexusResolved("ent_muzick", "Muzick indexer", "service")) hexis := newFakeHexis(t, restartCaps(), fixtureHexisExecuted("exec_1", "succeeded")) h := ecoHandler(t, nexus, nil, hexis) dec := router.Decision{ Utterance: "перезапусти muzick indexer", Intent: router.IntentAct, Slots: router.Slots{Text: "перезагрузить музик индексер", Fn: "restart", HasFn: true}, } h.handleHexisAct(ctx, dec) reqs := nexus.Requests() if len(reqs) == 0 { t.Fatal("nexus was never asked") } body := string(reqs[0].Body) if !strings.Contains(body, "muzick indexer") { t.Fatalf("nexus resolve body = %s, want the name he said", body) } } // TestAnEntityActReachesHexisInsteadOfAsking — the second half of #476. The // stage-3 gate thins an act with no allowlisted fn, and that question used to // be the whole turn, so the Hexis path was unreachable from voice or chat. func TestAnEntityActReachesHexisInsteadOfAsking(t *testing.T) { ctx := context.Background() nexus := newFakeNexus(t, fixtureNexusResolved("ent_muzick", "Muzick indexer", "service")) hexis := newFakeHexis(t, restartCaps(), fixtureHexisExecuted("exec_1", "succeeded")) h := ecoHandler(t, nexus, nil, hexis) dec := router.Decision{ Utterance: "перезапусти muzick indexer", Intent: router.IntentAct, Stage: 3, Clarify: true, Slots: router.Slots{Text: "restart status muzick indexer"}, } reply := h.hexisBeforeClarify(ctx, dec) if reply == "" { t.Fatal("a resolvable entity act must reach hexis rather than fall through to the question") } if hexis.Count("", "/api/v1") == 0 { t.Fatal("hexis was never contacted") } } // TestClarifyStillAsksWithoutHexis — the narrowing. No ecosystem, no change: // she asks exactly what she asked before. func TestClarifyStillAsksWithoutHexis(t *testing.T) { h, _, _ := newClarifyHandler(t) dec := router.Decision{ Utterance: "перезапусти muzick indexer", Intent: router.IntentAct, Stage: 3, Clarify: true, Slots: router.Slots{Text: "перезагрузить музик индексер"}, } if reply := h.hexisBeforeClarify(context.Background(), dec); reply != "" { t.Fatalf("no hexis must mean no reply, got %q", reply) } if _, asked := h.askClarify(voiceCtx(), dec); !asked { t.Fatal("she must still ask what to do") } }