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) } }