router: add TryFastPath tests and Route identity invariant (slice 10)
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTryFastPathMatchesReminderGrammar(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, ReminderGrammar())
|
||||
now := refNow()
|
||||
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "напомни позвонить маме завтра"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true")
|
||||
}
|
||||
d := fast.Decision
|
||||
if d.Intent != IntentReminder {
|
||||
t.Errorf("Intent = %q, want %q", d.Intent, IntentReminder)
|
||||
}
|
||||
if d.Stage != 0 {
|
||||
t.Errorf("Stage = %d, want 0", d.Stage)
|
||||
}
|
||||
if d.Producer != RouteProducerGrammar {
|
||||
t.Errorf("Producer = %q, want %q", d.Producer, RouteProducerGrammar)
|
||||
}
|
||||
if d.Confidence != 1.0 {
|
||||
t.Errorf("Confidence = %f, want 1.0", d.Confidence)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathMatchesWithWakeToken(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||||
now := refNow()
|
||||
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "Мэйвен который час"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true")
|
||||
}
|
||||
d := fast.Decision
|
||||
if d.Intent != IntentSystem {
|
||||
t.Errorf("Intent = %q, want %q", d.Intent, IntentSystem)
|
||||
}
|
||||
if d.Stage != 0 {
|
||||
t.Errorf("Stage = %d, want 0", d.Stage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathMissFallsThrough(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
now := refNow()
|
||||
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "как дела"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if fast.Matched {
|
||||
t.Fatal("expected Matched=false for unmatched utterance")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathGrammarOrderPreserved(t *testing.T) {
|
||||
acts := DefaultActMatcher{Fns: []string{"restart"}}
|
||||
grammars := StageZeroGrammars(acts)
|
||||
if len(grammars) == 0 {
|
||||
t.Fatal("StageZeroGrammars returned empty list")
|
||||
}
|
||||
|
||||
r := New(Config{
|
||||
Grammars: grammars,
|
||||
Classifier: nil,
|
||||
Extractor: Extractor{Time: StubDateTimeParser{}, Acts: acts},
|
||||
Threshold: 0.55,
|
||||
})
|
||||
|
||||
// Verify TryFastPath uses the same ordered list by checking grammar names.
|
||||
// We can't read r.grammars directly from outside the package, but we can
|
||||
// verify the count matches.
|
||||
if len(r.grammars) != len(grammars) {
|
||||
t.Errorf("Router.grammars length = %d, want %d", len(r.grammars), len(grammars))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathCapabilitySelection(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
now := refNow()
|
||||
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "maven, restart nginx"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true")
|
||||
}
|
||||
d := fast.Decision
|
||||
if d.Intent != IntentAct {
|
||||
t.Errorf("Intent = %q, want %q", d.Intent, IntentAct)
|
||||
}
|
||||
if !d.CapabilitySelection.Resolved {
|
||||
t.Error("CapabilitySelection.Resolved = false, want true")
|
||||
}
|
||||
if d.CapabilitySelection.Fn != "restart" {
|
||||
t.Errorf("CapabilitySelection.Fn = %q, want %q", d.CapabilitySelection.Fn, "restart")
|
||||
}
|
||||
if d.CapabilitySelection.Method != ActionResolutionGrammarMatcher {
|
||||
t.Errorf("CapabilitySelection.Method = %q, want %q", d.CapabilitySelection.Method, ActionResolutionGrammarMatcher)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathDoesNotReadMatchText(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||||
now := refNow()
|
||||
|
||||
// Pass an input where MatchText differs from Text. If any grammar
|
||||
// consumed MatchText, the result would differ from using Text alone.
|
||||
input := NormalizedInput{
|
||||
Text: "который час",
|
||||
MatchText: "totally different text that should not be used",
|
||||
}
|
||||
fast, err := r.TryFastPath(context.Background(), input, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true for time query")
|
||||
}
|
||||
if fast.Decision.Intent != IntentSystem {
|
||||
t.Errorf("Intent = %q, want %q (grammar should use Text, not MatchText)", fast.Decision.Intent, IntentSystem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteIdenticalBeforeAfter(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||||
r.grammars = append(r.grammars, ReminderGrammar())
|
||||
now := refNow()
|
||||
|
||||
utterances := []struct {
|
||||
text string
|
||||
intent Intent
|
||||
stage int
|
||||
}{
|
||||
{"напомни позвонить маме завтра", IntentReminder, 0},
|
||||
{"который час", IntentSystem, 0},
|
||||
{"как дела", IntentChat, 2}, // falls through to classifier (stage 2)
|
||||
}
|
||||
|
||||
for _, u := range utterances {
|
||||
d, err := r.Route(context.Background(), u.text, now)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Route: %v", u.text, err)
|
||||
continue
|
||||
}
|
||||
if d.Intent != u.intent {
|
||||
t.Errorf("%s: Intent = %q, want %q", u.text, d.Intent, u.intent)
|
||||
}
|
||||
if d.Stage != u.stage {
|
||||
t.Errorf("%s: Stage = %d, want %d", u.text, d.Stage, u.stage)
|
||||
}
|
||||
if u.stage == 0 && d.Producer != RouteProducerGrammar {
|
||||
t.Errorf("%s: Producer = %q, want %q", u.text, d.Producer, RouteProducerGrammar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathSourceAnchored(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, AgendaQueryGrammars()...)
|
||||
now := refNow()
|
||||
|
||||
// "что в календаре на завтра" — calendar-query names SourceCalendar.
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "что в календаре на завтра"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true")
|
||||
}
|
||||
d := fast.Decision
|
||||
if d.Source != SourceCalendar {
|
||||
t.Errorf("Source = %q, want %q", d.Source, SourceCalendar)
|
||||
}
|
||||
if !d.SourceAnchored {
|
||||
t.Error("SourceAnchored = false, want true (grammar named the destination)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryFastPathFillMatchedSlots(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, ReminderGrammar())
|
||||
now := refNow()
|
||||
|
||||
// "напомни в 11:00 позвонить маме" — grammar captures text, extractor fills time.
|
||||
fast, err := r.TryFastPath(context.Background(), NormalizedInput{Text: "напомни в 11:00 позвонить маме"}, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath: %v", err)
|
||||
}
|
||||
if !fast.Matched {
|
||||
t.Fatal("expected Matched=true")
|
||||
}
|
||||
d := fast.Decision
|
||||
if !d.Slots.HasTime {
|
||||
t.Error("HasTime = false, want true (fillMatchedSlots should fill time)")
|
||||
}
|
||||
if got, want := d.Slots.Time.Format("15:04"), "11:00"; got != want {
|
||||
t.Errorf("Time = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user