+package m31
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "onehouse/protocol"
+)
+
+func TestTopologyKeepsIndependentAddressRanges(t *testing.T) {
+ value, err := newTopology([]Module{
+ {Model: HostModel},
+ {Model: OutputModel, PulseWidths: map[int]time.Duration{15: 200 * time.Millisecond}},
+ {Model: AnalogModel},
+ {Model: InputModel},
+ {Model: MixedModel},
+ {Model: AnalogModel},
+ })
+ require.NoError(t, err)
+ require.Len(t, value.inputs, 32)
+ require.Len(t, value.outputs, 32)
+ require.Len(t, value.analog, 16)
+ require.Equal(t, "m4.di0", value.inputs[24].id)
+ require.Equal(t, "m4.do0", value.outputs[24].id)
+ require.EqualValues(t, 24, value.outputs[24].address)
+ require.Equal(t, "m5.ao0", value.analog[8].id)
+ require.EqualValues(t, 8, value.analog[8].address)
+ require.EqualValues(t, 200, value.outputs[23].pulse)
+
+ descriptor := value.descriptor()
+ require.Len(t, descriptor.Capabilities, 80)
+
+ for _, capability := range descriptor.Capabilities {
+ if capability.ID == "m1.do15" {
+ require.Equal(t, "pulse", capability.Type)
+ require.False(t, capability.ReportsState)
+ require.True(t, capability.AcceptsCommands)
+ }
+ }
+
+ id, err := protocol.NewID(time.Now())
+ require.NoError(t, err)
+
+ _, err = protocol.BuildDescriptor(id, protocol.Metadata{MessageID: id}, descriptor)
+ require.NoError(t, err)
+}
+
+func TestTopologyRejectsUnsafeModulesAndPulseSettings(t *testing.T) {
+ testCases := []struct {
+ name string
+ modules []Module
+ }{
+ {name: "requires a host"},
+ {name: "limits total modules", modules: make([]Module, 17)},
+ {name: "rejects unknown models", modules: []Module{{Model: "unknown"}}},
+ {name: "rejects expansion at slot zero", modules: []Module{{Model: MixedModel}}},
+ {name: "rejects another host", modules: []Module{{Model: HostModel}, {Model: HostModel}}},
+ {name: "rejects negative pulse channel", modules: []Module{{Model: HostModel, PulseWidths: map[int]time.Duration{-1: time.Second}}}},
+ {name: "rejects excess pulse channel", modules: []Module{{Model: HostModel, PulseWidths: map[int]time.Duration{8: time.Second}}}},
+ {name: "rejects pulse on input module", modules: []Module{{Model: HostModel}, {Model: InputModel, PulseWidths: map[int]time.Duration{0: time.Second}}}},
+ {name: "rejects short pulse", modules: []Module{{Model: HostModel, PulseWidths: map[int]time.Duration{0: time.Millisecond}}}},
+ {name: "rejects long pulse", modules: []Module{{Model: HostModel, PulseWidths: map[int]time.Duration{0: 65536 * time.Millisecond}}}},
+ {name: "rejects fractional millisecond", modules: []Module{{Model: HostModel, PulseWidths: map[int]time.Duration{0: 50*time.Millisecond + time.Nanosecond}}}},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ _, err := newTopology(tc.modules)
+ require.Error(t, err)
+ })
+ }
+}