package evdev import ( "errors" "testing" evdev "github.com/holoplot/go-evdev" "github.com/stretchr/testify/assert" mocks "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/evdev" ) func TestManager_Creation(t *testing.T) { t.Run("manager created successfully with caps lock off", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } assert.NotNil(t, m) assert.True(t, m.state.Available) assert.False(t, m.state.CapsLock) }) t.Run("manager created successfully with caps lock on", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, state: State{Available: true, CapsLock: true}, closeChan: make(chan struct{}), } assert.NotNil(t, m) assert.True(t, m.state.Available) assert.True(t, m.state.CapsLock) }) } func TestManager_GetState(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } state := m.GetState() assert.True(t, state.Available) assert.False(t, state.CapsLock) } func TestManager_Subscribe(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch := m.Subscribe("test-client") assert.NotNil(t, ch) count := 0 m.subscribers.Range(func(key string, ch chan State) bool { count++ return true }) assert.Equal(t, 1, count) } func TestManager_Unsubscribe(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch := m.Subscribe("test-client") count := 0 m.subscribers.Range(func(key string, ch chan State) bool { count++ return true }) assert.Equal(t, 1, count) m.Unsubscribe("test-client") count = 0 m.subscribers.Range(func(key string, ch chan State) bool { count++ return true }) assert.Equal(t, 0, count) select { case _, ok := <-ch: assert.False(t, ok, "channel should be closed") default: t.Error("channel should be closed") } } func TestManager_UpdateCapsLock(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch := m.Subscribe("test-client") ledStateOn := evdev.StateMap{ledCapslockKey: true} mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOn, nil).Once() go func() { m.readAndUpdateCapsLockState(0) }() newState := <-ch assert.True(t, newState.CapsLock) ledStateOff := evdev.StateMap{ledCapslockKey: false} mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledStateOff, nil).Once() go func() { m.readAndUpdateCapsLockState(0) }() newState = <-ch assert.False(t, newState.CapsLock) } func TestManager_Close(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().Close().Return(nil).Once() mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch1 := m.Subscribe("client1") ch2 := m.Subscribe("client2") m.Close() select { case _, ok := <-ch1: assert.False(t, ok, "channel 1 should be closed") default: t.Error("channel 1 should be closed") } select { case _, ok := <-ch2: assert.False(t, ok, "channel 2 should be closed") default: t.Error("channel 2 should be closed") } count := 0 m.subscribers.Range(func(key string, ch chan State) bool { count++ return true }) assert.Equal(t, 0, count) m.Close() } func TestIsKeyboard(t *testing.T) { tests := []struct { name string devName string expected bool }{ {"keyboard in name", "AT Translated Set 2 keyboard", true}, {"kbd in name", "USB kbd", true}, {"input and key", "input key device", true}, {"random device", "Mouse", false}, {"empty name", "", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().Name().Return(tt.devName, nil).Once() if !tt.expected { mockDevice.EXPECT().State(evdev.EvType(evKeyType)).Return(evdev.StateMap{}, nil).Maybe() } result := isKeyboard(mockDevice) assert.Equal(t, tt.expected, result) }) } } func TestIsKeyboard_ErrorHandling(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().Name().Return("", errors.New("device error")).Once() result := isKeyboard(mockDevice) assert.False(t, result) } func TestManager_MonitorDevice(t *testing.T) { t.Run("caps lock key press updates state", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) capsLockEvent := &evdev.InputEvent{ Type: evKeyType, Code: keyCapslockKey, Value: keyStateOn, } ledState := evdev.StateMap{ledCapslockKey: true} mockDevice.EXPECT().ReadOne().Return(capsLockEvent, nil).Once() mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once() mockDevice.EXPECT().ReadOne().Return(nil, errors.New("stop")).Maybe() mockDevice.EXPECT().Close().Return(nil).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch := m.Subscribe("test") go m.monitorDevice(mockDevice, 0) state := <-ch assert.True(t, state.CapsLock) m.Close() }) } func TestIsClosedError(t *testing.T) { tests := []struct { name string err error expected bool }{ {"nil error", nil, false}, {"closed error", errors.New("device closed"), true}, {"bad file descriptor", errors.New("bad file descriptor"), true}, {"other error", errors.New("some other error"), false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isClosedError(tt.err) assert.Equal(t, tt.expected, result) }) } } func TestNotifySubscribers(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().ReadOne().Return(nil, errors.New("test")).Maybe() mockDevice.EXPECT().Close().Return(nil).Maybe() m := &Manager{ devices: []EvdevDevice{mockDevice}, monitoredPaths: make(map[string]bool), state: State{Available: true, CapsLock: false}, closeChan: make(chan struct{}), } ch1 := m.Subscribe("client1") ch2 := m.Subscribe("client2") newState := State{Available: true, CapsLock: true} go m.notifySubscribers(newState) state1 := <-ch1 state2 := <-ch2 assert.Equal(t, newState, state1) assert.Equal(t, newState, state2) m.Close() } func TestReadInitialCapsLockState(t *testing.T) { t.Run("caps lock is on", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) ledState := evdev.StateMap{ ledCapslockKey: true, } mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once() result := readInitialCapsLockState(mockDevice) assert.True(t, result) }) t.Run("caps lock is off", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) ledState := evdev.StateMap{ ledCapslockKey: false, } mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once() result := readInitialCapsLockState(mockDevice) assert.False(t, result) }) t.Run("error reading LED state", func(t *testing.T) { mockDevice := mocks.NewMockEvdevDevice(t) mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once() result := readInitialCapsLockState(mockDevice) assert.False(t, result) }) } func TestHasInputGroupAccess(t *testing.T) { result := hasInputGroupAccess() t.Logf("hasInputGroupAccess: %v", result) }