diff --git a/cmd/mavend/chat_degrade_test.go b/cmd/mavend/chat_degrade_test.go new file mode 100644 index 0000000..c376aa0 --- /dev/null +++ b/cmd/mavend/chat_degrade_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "testing" + "time" + + "github.com/kami/maven/internal/llm" + "github.com/kami/maven/internal/router" +) + +// The chat path must answer when llama-server is down (Vikunja #45 step 4). +// Both halves of a turn call the model — the router and the replier — and each +// has its own floor: the cascade falls to the classifier, the replier falls to +// the stub. This wires a client at a closed port so both floors are exercised +// by a dial error rather than by a stubbed error value. +func TestChatAnswersWithNoLlamaServer(t *testing.T) { + h, _, _ := newClarifyHandler(t) + dead := llm.New("http://127.0.0.1:1", 500*time.Millisecond) + emb := router.NewHashEmbedder(1024) + h.recall.embedder = emb + h.router = buildRouter(emb, h.matcher, 0.55, pickLLMRouter(true, dead)) + h.replier = newLLMReplier(dead, nil) + + ctx := withDialogueID(context.Background(), dialogueIDFor(sourceText, "web")) + for _, utt := range []string{ + "привет", + "запиши что кофе закончился", + "что у меня сегодня", + } { + reply := h.handleText(ctx, "web", utt) + if reply == "" { + t.Errorf("%q answered with nothing; a dead model must degrade to the stub", utt) + } + } +} + +// daemonAPI.Chat reports an error only when the voice path was never wired. +// A turn that reaches handleText always carries text, which is what keeps +// mavweb's /api/chat off its error branch when the model is down. +func TestChatAPIErrsOnlyWhenUnwired(t *testing.T) { + d := &daemonAPI{} + if _, err := d.Chat(context.Background(), "web", "привет"); err == nil { + t.Fatal("an unwired daemon must say so") + } + d.chatFn = func(context.Context, string, string) string { return "" } + if _, err := d.Chat(context.Background(), "web", "привет"); err != nil { + t.Fatalf("a wired daemon must not error: %v", err) + } +}