package main import ( "io" "log" "os" "sync" "testing" "git.sr.ht/~michalr/go-satel" "github.com/stretchr/testify/assert" ) func TestSatelEventTypeFiltering(t *testing.T) { tested := MakeFilterByTypeOrIndex([]SatelChangeType{{satel.ArmedPartition}, {satel.PartitionFireAlarm}}, []int{}) mock := &GenericSyncMockFilter[satel.Event]{} tested.Then(mock) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.DoorOpened, 2, true)) tested.Call(makeTestSatelEvent(satel.PartitionAlarm, 3, true)) tested.Call(makeTestSatelEvent(satel.PartitionFireAlarm, 4, true)) tested.Call(makeTestSatelEvent(satel.TroublePart1, 5, true)) tested.Call(makeTestSatelEvent(satel.ZoneTamper, 6, true)) assert.Len(t, mock.collected, 2) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.PartitionFireAlarm, 4, true)) } func TestSatelEventTypeFiltering_NoAllowedEventTypesMeansAllAreAllowed(t *testing.T) { tested := MakeFilterByTypeOrIndex([]SatelChangeType{}, []int{}) mock := &GenericSyncMockFilter[satel.Event]{} tested.Then(mock) for index, ct := range SUPPORTED_CHANGE_TYPES { tested.Call(makeTestSatelEvent(ct, index, true)) } assert.Len(t, mock.collected, len(SUPPORTED_CHANGE_TYPES)) for index, ct := range SUPPORTED_CHANGE_TYPES { assert.Contains(t, mock.collected, makeTestSatelEvent(ct, index, true)) } } func TestSatelIndexFiltering(t *testing.T) { tested := MakeFilterByTypeOrIndex([]SatelChangeType{}, []int{1, 3}) mock := &GenericSyncMockFilter[satel.Event]{} tested.Then(mock) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.DoorOpened, 2, true)) tested.Call(makeTestSatelEvent(satel.PartitionAlarm, 3, true)) tested.Call(makeTestSatelEvent(satel.PartitionFireAlarm, 4, true)) tested.Call(makeTestSatelEvent(satel.TroublePart1, 5, true)) tested.Call(makeTestSatelEvent(satel.ZoneTamper, 6, true)) assert.Len(t, mock.collected, 2) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.PartitionAlarm, 3, true)) } func TestSatelIndexFiltering_NoAllowedEventTypesMeansAllAreAllowed(t *testing.T) { tested := MakeFilterByTypeOrIndex([]SatelChangeType{{satel.ArmedPartition}, {satel.PartitionFireAlarm}}, []int{}) mock := &GenericSyncMockFilter[satel.Event]{} myReasonableMaxIndex := 100 // I wanted to use math.MaxInt at first, but it's kind of a waste of time here tested.Then(mock) for i := 0; i < myReasonableMaxIndex; i++ { tested.Call(makeTestSatelEvent(satel.ArmedPartition, i, true)) } assert.Len(t, mock.collected, myReasonableMaxIndex) for i := 0; i < myReasonableMaxIndex; i++ { assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, i, true)) } } func TestSatelLastSeenFiltering(t *testing.T) { f, err := os.CreateTemp("", "TestSatelLastSeenFiltering") assert.NoError(t, err) tempFileName := f.Name() assert.NoError(t, f.Close()) defer os.Remove(f.Name()) fakeLog := log.New(io.Discard, "", log.Ltime) ds := MakeDataStore(fakeLog, tempFileName) tested := MakeFilterByLastSeen(&ds) mock := &GenericSyncMockFilter[satel.Event]{} tested.Then(mock) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) assert.Len(t, mock.collected, 3) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 2, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, false)) } func TestSatelLastSeenFilteringWithPersistence(t *testing.T) { f, err := os.CreateTemp("", "TestSatelLastSeenFilteringWithPersistence") assert.NoError(t, err) tempFileName := f.Name() assert.NoError(t, f.Close()) defer os.Remove(f.Name()) fakeLog := log.New(io.Discard, "", log.Ltime) ds := MakeDataStore(fakeLog, tempFileName) tested := MakeFilterByLastSeen(&ds) mock := &GenericSyncMockFilter[satel.Event]{} tested.Then(mock) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) tested.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) assert.Len(t, mock.collected, 3) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 2, true)) assert.Contains(t, mock.collected, makeTestSatelEvent(satel.ArmedPartition, 1, false)) tested = nil mock = nil ds2 := MakeDataStore(fakeLog, tempFileName) tested2 := MakeFilterByLastSeen(&ds2) mock2 := &GenericSyncMockFilter[satel.Event]{} tested2.Then(mock2) tested2.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) tested2.Call(makeTestSatelEvent(satel.ArmedPartition, 1, false)) tested2.Call(makeTestSatelEvent(satel.ArmedPartition, 1, true)) tested2.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) tested2.Call(makeTestSatelEvent(satel.ArmedPartition, 2, true)) assert.Len(t, mock2.collected, 1) assert.Contains(t, mock2.collected, makeTestSatelEvent(satel.ArmedPartition, 1, true)) } type MockSleeper struct { ch *chan<- interface{} callCount int } func (self *MockSleeper) Sleep(ch chan<- interface{}) { if self.ch == nil { self.ch = &ch } self.callCount += 1 } type GenericSyncMockFilter[T any] struct { SyncFilterImpl[T] collected []T } func (self *GenericSyncMockFilter[T]) Call(msg T) { self.collected = append(self.collected, msg) self.CallNext(msg) } type SyncMockFilter = GenericSyncMockFilter[GenericMessage] func TestSyncCollect(t *testing.T) { testEvents := make(chan GenericMessage) wg := sync.WaitGroup{} tested := CollectFromChannel[GenericMessage]{} mock := &SyncMockFilter{} mock2 := &SyncMockFilter{} tested.Then(mock).Then(mock2) wg.Add(1) tested.Collect(testEvents, &wg, func() { wg.Done() }) testEvents <- makeGenericMessage(satel.ArmedPartition, 1, true) testEvents <- makeGenericMessage(satel.DoorOpened, 2, true) testEvents <- makeGenericMessage(satel.PartitionAlarm, 3, true) testEvents <- makeGenericMessage(satel.PartitionFireAlarm, 4, true) testEvents <- makeGenericMessage(satel.TroublePart1, 5, true) testEvents <- makeGenericMessage(satel.ZoneTamper, 6, true) close(testEvents) wg.Wait() assert.Len(t, mock.collected, 6) assert.Contains(t, mock.collected, makeGenericMessage(satel.ArmedPartition, 1, true)) assert.Contains(t, mock.collected, makeGenericMessage(satel.DoorOpened, 2, true)) assert.Contains(t, mock.collected, makeGenericMessage(satel.PartitionAlarm, 3, true)) assert.Contains(t, mock.collected, makeGenericMessage(satel.PartitionFireAlarm, 4, true)) assert.Contains(t, mock.collected, makeGenericMessage(satel.TroublePart1, 5, true)) assert.Contains(t, mock.collected, makeGenericMessage(satel.ZoneTamper, 6, true)) assert.Len(t, mock2.collected, 6) assert.Contains(t, mock2.collected, makeGenericMessage(satel.ArmedPartition, 1, true)) assert.Contains(t, mock2.collected, makeGenericMessage(satel.DoorOpened, 2, true)) assert.Contains(t, mock2.collected, makeGenericMessage(satel.PartitionAlarm, 3, true)) assert.Contains(t, mock2.collected, makeGenericMessage(satel.PartitionFireAlarm, 4, true)) assert.Contains(t, mock2.collected, makeGenericMessage(satel.TroublePart1, 5, true)) assert.Contains(t, mock2.collected, makeGenericMessage(satel.ZoneTamper, 6, true)) } func TestThrottleSync(t *testing.T) { wg := sync.WaitGroup{} fakeLog := log.New(io.Discard, "", log.Ltime) mockSleeper := MockSleeper{nil, 0} var ( tplMessageTest1 = satel.BasicEventElement{Type: satel.ArmedPartition, Index: 1, Value: true} tplMessageTest2 = satel.BasicEventElement{Type: satel.ZoneViolation, Index: 2, Value: true} tplMessageTest3 = satel.BasicEventElement{Type: satel.ArmedPartition, Index: 1, Value: false} tplMessageTest4 = satel.BasicEventElement{Type: satel.ZoneViolation, Index: 2, Value: false} ) tested := MakeThrottleSync(&mockSleeper, fakeLog, &wg) mock := &SyncMockFilter{} tested.Then(mock) tested.Call(GenericMessage{[]satel.BasicEventElement{tplMessageTest1}}) tested.Call(GenericMessage{[]satel.BasicEventElement{tplMessageTest2}}) tested.Call(GenericMessage{[]satel.BasicEventElement{tplMessageTest3}}) *mockSleeper.ch <- nil tested.Call(GenericMessage{[]satel.BasicEventElement{tplMessageTest4}}) tested.Close() wg.Wait() assert.Equal(t, 2, mockSleeper.callCount) assert.Len(t, mock.collected, 2) assert.Contains(t, mock.collected[0].Messages, tplMessageTest2) assert.Contains(t, mock.collected[0].Messages, tplMessageTest3) assert.Len(t, mock.collected[0].Messages, 2) assert.Contains(t, mock.collected[1].Messages, tplMessageTest4) assert.Len(t, mock.collected[1].Messages, 1) } func TestConvert_failsWhenNotConverting(t *testing.T) { a := assert.New(t) tested := MakeConvert(func(in int) GenericMessage { a.Equal(in, 1) return GenericMessage{} }) mock := &GenericSyncMockFilter[int]{} a.Panics(func() { tested.Then(mock) tested.Call(1) }) } func TestConvert(t *testing.T) { a := assert.New(t) numCalled := 0 tested := MakeConvert(func(in int) GenericMessage { a.Equal(in, 1) numCalled += 1 return GenericMessage{} }) mock := &SyncMockFilter{} tested.ConvertTo(mock) tested.Call(1) a.Equal(numCalled, 1) }