router: update all Route callers for NormalizedInput + add invariant tests (slice 11)
Migrate 54 test call sites to construct NormalizedInput{Text: ...}.
Add invariant tests:
- TestNormalizedInputReachesRouteIntact: ingress NormalizedInput reaches Route
- TestTryFastPathReceivesMatchText: TryFastPath gets the same input
- TestMatchTextDoesNotChangeRouting: same Text + different MatchText → same Decision
- TestDecisionUtteranceEqualsInputText: Decision.Utterance == input.Text
This commit is contained in:
@@ -154,7 +154,7 @@ func TestRouteIdenticalBeforeAfter(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, u := range utterances {
|
||||
d, err := r.Route(context.Background(), u.text, now)
|
||||
d, err := r.Route(context.Background(), NormalizedInput{Text: u.text}, now)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Route: %v", u.text, err)
|
||||
continue
|
||||
@@ -214,3 +214,137 @@ func TestTryFastPathFillMatchedSlots(t *testing.T) {
|
||||
t.Errorf("Time = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNormalizedInputReachesRouteIntact pins that the NormalizedInput
|
||||
// constructed at ingress arrives at Router.Route without reconstruction.
|
||||
func TestNormalizedInputReachesRouteIntact(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||||
now := refNow()
|
||||
|
||||
input := NormalizedInput{
|
||||
Text: "который час",
|
||||
MatchText: "который час",
|
||||
Source: InputSourceText,
|
||||
}
|
||||
d, err := r.Route(context.Background(), input, now)
|
||||
if err != nil {
|
||||
t.Fatalf("Route: %v", err)
|
||||
}
|
||||
if d.Intent != IntentSystem {
|
||||
t.Errorf("Intent = %q, want %q", d.Intent, IntentSystem)
|
||||
}
|
||||
if d.Utterance != input.Text {
|
||||
t.Errorf("Utterance = %q, want %q (Decision.Utterance must equal input.Text)", d.Utterance, input.Text)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTryFastPathReceivesMatchText pins that TryFastPath receives the
|
||||
// same NormalizedInput that Route was given (including MatchText).
|
||||
// Today no grammar reads MatchText, so the result must be identical
|
||||
// whether MatchText is set or empty — this freezes the dark-data contract.
|
||||
func TestTryFastPathReceivesMatchText(t *testing.T) {
|
||||
r := newTestRouter(t, 0.0)
|
||||
r.grammars = append(r.grammars, SystemTimeDateGrammars()...)
|
||||
now := refNow()
|
||||
|
||||
without := NormalizedInput{Text: "который час"}
|
||||
with := NormalizedInput{Text: "который час", MatchText: "который час"}
|
||||
|
||||
fastWithout, err := r.TryFastPath(context.Background(), without, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath (without): %v", err)
|
||||
}
|
||||
fastWith, err := r.TryFastPath(context.Background(), with, now)
|
||||
if err != nil {
|
||||
t.Fatalf("TryFastPath (with): %v", err)
|
||||
}
|
||||
if fastWithout.Matched != fastWith.Matched {
|
||||
t.Errorf("Matched: without=%v, with=%v", fastWithout.Matched, fastWith.Matched)
|
||||
}
|
||||
if fastWithout.Matched && fastWith.Matched {
|
||||
if fastWithout.Decision.Intent != fastWith.Decision.Intent {
|
||||
t.Errorf("Intent: without=%q, with=%q", fastWithout.Decision.Intent, fastWith.Decision.Intent)
|
||||
}
|
||||
if fastWithout.Decision.Confidence != fastWith.Decision.Confidence {
|
||||
t.Errorf("Confidence: without=%f, with=%f", fastWithout.Decision.Confidence, fastWith.Decision.Confidence)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMatchTextDoesNotChangeRouting pins the dark-data invariant:
|
||||
// same Text, different MatchText → same Decision. This must hold until
|
||||
// an explicit later slice opts a consumer into MatchText.
|
||||
func TestMatchTextDoesNotChangeRouting(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
|
||||
want Intent
|
||||
}{
|
||||
{"который час", IntentSystem},
|
||||
{"напомни позвонить маме завтра", IntentReminder},
|
||||
}
|
||||
|
||||
for _, u := range utterances {
|
||||
without := NormalizedInput{Text: u.text}
|
||||
with := NormalizedInput{Text: u.text, MatchText: NormalizeMatchText(u.text)}
|
||||
|
||||
dWithout, err := r.Route(context.Background(), without, now)
|
||||
if err != nil {
|
||||
t.Errorf("%s (without MatchText): Route: %v", u.text, err)
|
||||
continue
|
||||
}
|
||||
dWith, err := r.Route(context.Background(), with, now)
|
||||
if err != nil {
|
||||
t.Errorf("%s (with MatchText): Route: %v", u.text, err)
|
||||
continue
|
||||
}
|
||||
if dWithout.Intent != dWith.Intent {
|
||||
t.Errorf("%s: Intent changed: without=%q, with=%q", u.text, dWithout.Intent, dWith.Intent)
|
||||
}
|
||||
if dWithout.Confidence != dWith.Confidence {
|
||||
t.Errorf("%s: Confidence changed: without=%f, with=%f", u.text, dWithout.Confidence, dWith.Confidence)
|
||||
}
|
||||
if dWithout.Stage != dWith.Stage {
|
||||
t.Errorf("%s: Stage changed: without=%d, with=%d", u.text, dWithout.Stage, dWith.Stage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecisionUtteranceEqualsInputText pins that Decision.Utterance is
|
||||
// always input.Text, regardless of which cascade path was taken.
|
||||
func TestDecisionUtteranceEqualsInputText(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
|
||||
want Intent
|
||||
}{
|
||||
{"который час", IntentSystem},
|
||||
{"напомни позвонить маме завтра", IntentReminder},
|
||||
{"как дела", IntentChat},
|
||||
}
|
||||
|
||||
for _, u := range utterances {
|
||||
input := NormalizedInput{Text: u.text}
|
||||
d, err := r.Route(context.Background(), input, now)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Route: %v", u.text, err)
|
||||
continue
|
||||
}
|
||||
if d.Intent != u.want {
|
||||
t.Errorf("%s: Intent = %q, want %q", u.text, d.Intent, u.want)
|
||||
continue
|
||||
}
|
||||
if d.Utterance != u.text {
|
||||
t.Errorf("%s: Decision.Utterance = %q, want %q", u.text, d.Utterance, u.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user