using KronaStaffTerminal.Models; using KronaStaffTerminal.Services; using KronaStaffTerminal.Tests.Mocks; using Xunit; namespace KronaStaffTerminal.Tests; public class ScannerRecoveryTests { [Fact] public void Scanner_SimulatesUsbDisconnectAndFiresConnectionLost() { using var scanner = new MockFingerprintScanner(); scanner.Connect(0, Array.Empty<(int, string)>()); Assert.True(scanner.IsConnected); Assert.Equal(ScannerState.Connected, scanner.State); var connectionLostFired = false; ScannerState? reportedState = null; scanner.ConnectionLost += () => connectionLostFired = true; scanner.StateChanged += (state, _) => reportedState = state; // Simulate physical USB drop scanner.SimulateDisconnect(); Assert.False(scanner.IsConnected); Assert.Equal(ScannerState.Disconnected, scanner.State); Assert.True(connectionLostFired); Assert.Equal(ScannerState.Disconnected, reportedState); } [Fact] public void Scanner_SimulatesUsbReconnectAndRestoresReadyState() { using var scanner = new MockFingerprintScanner(); scanner.Connect(0, Array.Empty<(int, string)>()); scanner.SimulateDisconnect(); Assert.Equal(ScannerState.Disconnected, scanner.State); ScannerState? reportedState = null; scanner.StateChanged += (state, _) => reportedState = state; // Simulate USB re-plug scanner.SimulateReconnect(); Assert.True(scanner.IsConnected); Assert.Equal(ScannerState.Connected, scanner.State); Assert.Equal(ScannerState.Connected, reportedState); } [Fact] public void AdminPinVerification_OperatesNormallyWhenScannerIsDisconnected() { using var scanner = new MockFingerprintScanner(); scanner.SimulateDisconnect(); Assert.Equal(ScannerState.Disconnected, scanner.State); // Verify that AdminPinService functions 100% independently of hardware scanner state var (salt, hash, iterations) = AdminPinService.Hash("5555"); var settings = new TerminalSettings { AdminPinSalt = salt, AdminPinHash = hash, AdminPinIterations = iterations }; var now = DateTimeOffset.UtcNow; var allowed = AdminPinService.VerifyAndRecord( "5555", settings, now, out var error, out var rehashNeeded); Assert.True(allowed); Assert.Null(error); Assert.False(rehashNeeded); } [Fact] public async Task Scanner_AutoReconnectLogic_CallsConnectSuccessfully() { using var scanner = new MockFingerprintScanner(); scanner.Connect(0, Array.Empty<(int, string)>()); Assert.Equal(1, scanner.ConnectCallCount); var reconnectTcs = new TaskCompletionSource(); scanner.ConnectionLost += async () => { // Simulate typical application retry loop int retries = 0; while (retries < 5) { await Task.Delay(10); try { scanner.Connect(0, Array.Empty<(int, string)>()); if (scanner.IsConnected) { reconnectTcs.TrySetResult(); return; } } catch { } retries++; } reconnectTcs.TrySetException(new Exception("Failed to reconnect")); }; scanner.SimulateDisconnect(); // Emulate the hardware coming back after a short delay await Task.Delay(20); scanner.AddVirtualDevice(0, "SLK20R", "SERIAL"); // ensure device is discoverable if it was cleared await reconnectTcs.Task; Assert.True(scanner.ConnectCallCount > 1); Assert.Equal(ScannerState.Connected, scanner.State); } }