krona-staff/apps/KronaStaffTerminal.Tests/ScannerRecoveryTests.cs

117 lines
3.9 KiB
C#

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, TestContext.Current.CancellationToken);
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, TestContext.Current.CancellationToken);
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);
}
}