da9114b623
Owner explicitly requested direct commits to master; bypass the branch-only hook.
112 lines
4.1 KiB
Go
112 lines
4.1 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/kami/maven/internal/dialogue"
|
||
"github.com/kami/maven/internal/router"
|
||
)
|
||
|
||
func TestRememberTurnKeepsIntentIndependentTranscriptInSpeakingOrder(t *testing.T) {
|
||
now := time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)
|
||
h := &reactiveHandler{
|
||
now: func() time.Time { return now },
|
||
dialogueSessions: dialogue.NewSessionStore(time.Hour),
|
||
}
|
||
ctx := context.Background()
|
||
turns := []router.Decision{
|
||
{Intent: router.IntentFact, Utterance: "я купил новый монитор", Slots: router.Slots{Key: "purchase", Value: "новый монитор", HasKey: true}},
|
||
{Intent: router.IntentQuery, Utterance: "а он большой?", Slots: router.Slots{Text: "normalized query"}},
|
||
{Intent: router.IntentChat, Utterance: "кажется, я переплатил", Slots: router.Slots{Text: "normalized chat"}},
|
||
{Intent: router.IntentQuery, Utterance: "стоит его вернуть?", Slots: router.Slots{Text: "normalized return query"}},
|
||
}
|
||
for i, dec := range turns {
|
||
prev := h.dialogueSessions.Get(voiceDialogueID, now)
|
||
h.rememberTurn(ctx, prev, dec, now.Add(time.Duration(i)*time.Second))
|
||
}
|
||
|
||
got := h.dialogueSessions.Get(voiceDialogueID, now.Add(4*time.Second))
|
||
if got == nil {
|
||
t.Fatal("no dialogue session")
|
||
}
|
||
if got.Utterance != turns[3].Utterance {
|
||
t.Fatalf("current utterance = %q, want %q", got.Utterance, turns[3].Utterance)
|
||
}
|
||
want := []string{turns[0].Utterance, turns[1].Utterance, turns[2].Utterance}
|
||
if len(got.History) != len(want) {
|
||
t.Fatalf("history = %+v, want %d prior turns", got.History, len(want))
|
||
}
|
||
for i := range want {
|
||
if got.History[i].Text != want[i] {
|
||
t.Errorf("history[%d] = %q, want %q", i, got.History[i].Text, want[i])
|
||
}
|
||
}
|
||
|
||
// actionChat runs after rememberTurn. It must receive only prior turns;
|
||
// handing over the current turn here would duplicate the model's user input.
|
||
history := h.chatHistory(ctx)
|
||
if len(history) != len(want) {
|
||
t.Fatalf("chat history = %+v, want exactly the prior turns", history)
|
||
}
|
||
for _, turn := range history {
|
||
if turn.Text == got.Utterance {
|
||
t.Fatalf("current utterance was duplicated into chat history: %+v", history)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestSessionAsTurnReadsLegacySlotText(t *testing.T) {
|
||
legacy := &dialogue.Session{
|
||
Intent: dialogue.IntentQuery,
|
||
Slots: dialogue.Slots{Text: "старый сохранённый вопрос"},
|
||
}
|
||
if got := sessionAsTurn(legacy).Text; got != legacy.Slots.Text {
|
||
t.Fatalf("legacy turn text = %q, want %q", got, legacy.Slots.Text)
|
||
}
|
||
}
|
||
|
||
func TestExplicitConversationOpenerKeepsCrossIntentSessionAlive(t *testing.T) {
|
||
now := time.Date(2026, 8, 13, 1, 0, 0, 0, time.UTC)
|
||
h := &reactiveHandler{
|
||
now: func() time.Time { return now },
|
||
dialogueSessions: dialogue.NewSessionStore(2 * time.Minute),
|
||
}
|
||
ctx := context.Background()
|
||
h.rememberTurn(ctx, nil, router.Decision{
|
||
Intent: router.IntentFact, Utterance: "давай поболтаем: я купил новый монитор",
|
||
Slots: router.Slots{Key: "purchase", Value: "новый монитор", HasKey: true},
|
||
}, now)
|
||
|
||
later := now.Add(10 * time.Minute)
|
||
prev := h.dialogueSessions.Get(voiceDialogueID, later)
|
||
if prev == nil {
|
||
t.Fatal("explicit conversation expired at the ordinary two-minute TTL")
|
||
}
|
||
if !prev.Conversational || prev.TTL != 15*time.Minute {
|
||
t.Fatalf("conversation state = %+v, want conversational 15m session", prev)
|
||
}
|
||
got := followUpMerge(prev, router.Decision{
|
||
Intent: router.IntentQuery, Utterance: "а он большой?",
|
||
}, later)
|
||
if got.Intent != router.IntentChat {
|
||
t.Fatalf("anaphoric follow-up intent = %s, want chat", got.Intent)
|
||
}
|
||
}
|
||
|
||
func TestConversationOpenerDoesNotMatchAnotherDavaiCommand(t *testing.T) {
|
||
if opensConversation("давай запишем новый монитор") {
|
||
t.Fatal("an ordinary cooperative command opened a conversation")
|
||
}
|
||
for _, text := range []string{
|
||
"давай поговорим: я купил монитор",
|
||
"давайте пообщаемся",
|
||
"let's talk: I bought a monitor",
|
||
} {
|
||
if !opensConversation(text) {
|
||
t.Errorf("%q did not open a conversation", text)
|
||
}
|
||
}
|
||
}
|