package main import ( "testing" ) // newTestOSCSource builds an OSC source with the given config for mapping tests, // without binding any socket. func newTestOSCSource(cfg OSCConfig) *OSCSource { sc := &SourceConfig{Name: "osc", Type: "osc", OSC: cfg} dev := NewDevice(&DeviceConfig{Name: "test", Zones: 6, IntegrationID: 1}) binding := dev.RegisterSource(sc.Name, 0, 0, "") return newOSCSource(sc, dev, binding) } // TestOSCMonitorAddressKnown verifies the symmetric address mapping for the // modeled reports: zone level, scene, and occupancy. func TestOSCMonitorAddressKnown(t *testing.T) { s := newTestOSCSource(OSCConfig{Prefix: "/lutron"}) cases := []struct { name string ev MonitorEvent wantAddr string wantArgs int }{ { name: "zone level", ev: MonitorEvent{Family: "DEVICE", Fields: []string{"1", "2", "14", "100.00"}}, wantAddr: "/lutron/zone/2/level", wantArgs: 1, }, { name: "scene", ev: MonitorEvent{Family: "DEVICE", Fields: []string{"1", "141", "7", "3"}}, wantAddr: "/lutron/scene", wantArgs: 1, }, { name: "occupancy", ev: MonitorEvent{Family: "GROUP", Fields: []string{"1", "3", "3"}}, wantAddr: "/lutron/group/1/occupancy", wantArgs: 1, }, } for _, tc := range cases { addr, args := s.monitorAddress(tc.ev) if addr != tc.wantAddr { t.Errorf("%s: addr = %q, want %q", tc.name, addr, tc.wantAddr) } if len(args) != tc.wantArgs { t.Errorf("%s: %d args, want %d", tc.name, len(args), tc.wantArgs) } } } // TestOSCMonitorAddressGenericFallback verifies an unmodeled report maps to the // generic /monitor//... address with the trailing field as the value. func TestOSCMonitorAddressGenericFallback(t *testing.T) { s := newTestOSCSource(OSCConfig{Prefix: "/lutron"}) // A button press: ~DEVICE,1,70,3 -> /lutron/monitor/device/1/70 arg 3. addr, args := s.monitorAddress(MonitorEvent{Family: "DEVICE", Fields: []string{"1", "70", "3"}}) if addr != "/lutron/monitor/device/1/70" { t.Errorf("addr = %q, want /lutron/monitor/device/1/70", addr) } if len(args) != 1 { t.Fatalf("%d args, want 1", len(args)) } if v, ok := args[0].(int32); !ok || v != 3 { t.Errorf("arg = %v (%T), want int32(3)", args[0], args[0]) } } // TestOSCLevelArg verifies the float and integer zone-level encodings. func TestOSCLevelArg(t *testing.T) { flt := true s := newTestOSCSource(OSCConfig{Prefix: "/lutron", LevelAsFloat: &flt}) if v, ok := s.levelArg(100).(float32); !ok || v != 1.0 { t.Errorf("float level = %v (%T), want float32(1)", v, v) } no := false si := newTestOSCSource(OSCConfig{Prefix: "/lutron", LevelAsFloat: &no}) if v, ok := si.levelArg(100).(int32); !ok || v != 255 { t.Errorf("int level = %v (%T), want int32(255)", v, v) } } // TestNewOSCSourceResolvesDestinations verifies stream_to destinations resolve and // bad entries are skipped, and that the family filter is built. func TestNewOSCSourceResolvesDestinations(t *testing.T) { s := newTestOSCSource(OSCConfig{ Prefix: "/lutron", StreamTo: []string{"127.0.0.1:9001", "not a host:port:::"}, Monitor: MonitorForward{Families: []string{"device", "GROUP"}}, }) if len(s.dests) != 1 { t.Errorf("resolved %d destinations, want 1 (one bad entry skipped)", len(s.dests)) } if !s.families["DEVICE"] || !s.families["GROUP"] { t.Errorf("family filter = %v, want DEVICE and GROUP", s.families) } }