FIX 1 — UDP 9100 binding race: SprtDiscoveryServiceTests and
CompositeDiscoveryServiceTests both use exclusive UDP 9100 binds. xunit
runs test classes concurrently by default, causing the tests to fail
intermittently with AddressAlreadyInUse. Add [Collection("udp-9100")]
attribute to both classes so they run serially instead.
FIX 2 — Design doc: CompositeDiscoveryService does NOT forward the callback
down to children; it raises the caller's callback itself after all children
finish, once per unique IP. Rewrite the relevant sentence to describe the
real behavior.
Verify: 30 tests pass (x2), build clean, no production code touched.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
103 lines
3.5 KiB
C#
103 lines
3.5 KiB
C#
using System.Text;
|
|
using Inspectron.Epson;
|
|
|
|
namespace Inspectron.Epson.Tests;
|
|
|
|
[Collection("udp-9100")]
|
|
public class SprtDiscoveryServiceTests
|
|
{
|
|
// The exact 39 bytes the printer returned on the bench:
|
|
// 17 chars of dashed MAC, 12 spaces, 10 chars of serial.
|
|
private static byte[] RealReply() =>
|
|
Encoding.ASCII.GetBytes("28-0e-8b-3d-ca-f1 2608120478");
|
|
|
|
[Fact]
|
|
public void BuildProbe_matches_the_captured_easyset_query()
|
|
{
|
|
byte[] expected =
|
|
{
|
|
0x53, 0x50, 0x52, 0x54, 0x2D, 0x49, 0x6E, 0x66, 0x6F, 0x72,
|
|
0x00, 0x00, 0x0D
|
|
};
|
|
|
|
Assert.Equal(expected, SprtDiscoveryService.BuildProbe());
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseReply_extracts_mac_and_serial()
|
|
{
|
|
bool ok = SprtDiscoveryService.TryParseReply(RealReply(), "10.1.252.219", out var printer);
|
|
|
|
Assert.True(ok);
|
|
Assert.NotNull(printer);
|
|
Assert.Equal("10.1.252.219", printer!.IPAddress);
|
|
Assert.Equal("28-0e-8b-3d-ca-f1", printer.MACAddress);
|
|
Assert.Equal("2608120478", printer.Serial);
|
|
Assert.Equal("POS895", printer.ModelName);
|
|
Assert.Equal(9100, printer.Port);
|
|
Assert.Equal("SPRT", printer.ServiceType);
|
|
Assert.Equal(RealReply(), printer.RawResponse);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseReply_rejects_a_payload_shorter_than_the_mac()
|
|
{
|
|
bool ok = SprtDiscoveryService.TryParseReply(
|
|
Encoding.ASCII.GetBytes("28-0e-8b"), "10.1.252.219", out var printer);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(printer);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseReply_rejects_a_payload_whose_head_is_not_a_mac()
|
|
{
|
|
// Right length, wrong shape - an unrelated broadcast landing on our port.
|
|
bool ok = SprtDiscoveryService.TryParseReply(
|
|
Encoding.ASCII.GetBytes("hello there, not a mac at all, nope!!!!!"),
|
|
"10.1.252.219", out var printer);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(printer);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseReply_rejects_a_mac_with_non_hex_digits()
|
|
{
|
|
bool ok = SprtDiscoveryService.TryParseReply(
|
|
Encoding.ASCII.GetBytes("zz-0e-8b-3d-ca-f1 2608120478"),
|
|
"10.1.252.219", out var printer);
|
|
|
|
Assert.False(ok);
|
|
Assert.Null(printer);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryParseReply_handles_a_reply_with_no_serial()
|
|
{
|
|
bool ok = SprtDiscoveryService.TryParseReply(
|
|
Encoding.ASCII.GetBytes("28-0e-8b-3d-ca-f1"), "10.1.252.219", out var printer);
|
|
|
|
Assert.True(ok);
|
|
Assert.Equal("", printer!.Serial);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DiscoverPrintersAsync_throws_when_it_cannot_bind_port_9100()
|
|
{
|
|
// Hold UDP 9100 so the service cannot bind it. It must throw rather than
|
|
// swallow the failure and return empty: CompositeDiscoveryService needs to
|
|
// tell "this pass is broken" apart from "the network genuinely has no SPRT
|
|
// printers on it". Swallowing it here would let a network fault look like
|
|
// a clean empty result, which is exactly what let live Epson printers get
|
|
// silently deregistered when discovery quietly "succeeded" with nothing.
|
|
using var blocker = new System.Net.Sockets.UdpClient();
|
|
blocker.Client.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 9100));
|
|
|
|
var service = new SprtDiscoveryService();
|
|
|
|
await Assert.ThrowsAsync<System.Net.Sockets.SocketException>(
|
|
() => service.DiscoverPrintersAsync(TimeSpan.FromMilliseconds(200)));
|
|
}
|
|
}
|