5e66aa8f22
dialogue.Slots gained Value in 925ce22, but toDialogueSlots never copied
it, so a clarifying answer carrying a fact payload still landed nowhere:
clarify.go:202 sends the answer through the converter, and the SlotValue
arm reads answer.Value.
Both converters now carry every field. TestSlotsParity compares the two
field sets by name and type; TestSlotsRoundTrip populates every router
field and checks the round trip, and fails the fixture itself when a new
field is left zero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QChoBS5qJSrCV98oNUnHNU
72 lines
2.1 KiB
Go
72 lines
2.1 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/kami/maven/internal/dialogue"
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// TestSlotsParity — dialogue.Slots is a hand-kept copy of router.Slots
|
|
// (dialogue must not import router: import cycle). Drift is silent, so this
|
|
// test compares the two field sets by name and type. If it fails, add the new
|
|
// field to both structs AND to toDialogueSlots/applyDialogueSlots in
|
|
// followup.go — do not relax the test.
|
|
func TestSlotsParity(t *testing.T) {
|
|
fields := func(v any) map[string]string {
|
|
rt := reflect.TypeOf(v)
|
|
out := make(map[string]string, rt.NumField())
|
|
for i := 0; i < rt.NumField(); i++ {
|
|
f := rt.Field(i)
|
|
out[f.Name] = f.Type.String()
|
|
}
|
|
return out
|
|
}
|
|
rf, df := fields(router.Slots{}), fields(dialogue.Slots{})
|
|
for name, typ := range rf {
|
|
dt, ok := df[name]
|
|
if !ok {
|
|
t.Errorf("router.Slots.%s (%s) missing from dialogue.Slots", name, typ)
|
|
continue
|
|
}
|
|
if dt != typ {
|
|
t.Errorf("field %s: router has %s, dialogue has %s", name, typ, dt)
|
|
}
|
|
}
|
|
for name, typ := range df {
|
|
if _, ok := rf[name]; !ok {
|
|
t.Errorf("dialogue.Slots.%s (%s) missing from router.Slots", name, typ)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSlotsRoundTrip — the converters carry every field. A field the parity
|
|
// test accepts can still be dropped in transit, so round-trip a fully
|
|
// populated value and compare.
|
|
func TestSlotsRoundTrip(t *testing.T) {
|
|
full := router.Slots{
|
|
Time: time.Date(2026, 8, 2, 11, 0, 0, 0, time.UTC),
|
|
HasTime: true,
|
|
Fn: "restart",
|
|
Args: []string{"nginx"},
|
|
HasFn: true,
|
|
Key: "water",
|
|
Value: `"drank"`,
|
|
HasKey: true,
|
|
Text: "выпил воды",
|
|
}
|
|
// Every field must be non-zero, or the round-trip proves nothing.
|
|
rv := reflect.ValueOf(full)
|
|
for i := 0; i < rv.NumField(); i++ {
|
|
if rv.Field(i).IsZero() {
|
|
t.Fatalf("field %s is zero: extend this fixture so the round-trip covers it",
|
|
rv.Type().Field(i).Name)
|
|
}
|
|
}
|
|
if got := applyDialogueSlots(router.Slots{}, toDialogueSlots(full)); !reflect.DeepEqual(got, full) {
|
|
t.Errorf("round-trip lost a slot:\n got %+v\nwant %+v", got, full)
|
|
}
|
|
}
|