amend POS895 plan: merge discovery tasks, drop network-dependent test
Pre-flight scan found two things the plan mandated that a reviewer would rightly flag: Task 1 committed a DiscoverPrintersAsync that threw NotImplementedException until Task 2 replaced it, and a unit test asserted an empty result from a real UDP broadcast, which passes on a laptop and fails on any network with a POS895 on it. Tasks 1 and 2 are now one task delivering the complete service, and the network-dependent test is gone. The deterministic bind-failure test still covers the degradation path. Six tasks now. Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
This commit is contained in:
@@ -19,9 +19,9 @@
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Test project, `DiscoveredPrinter.Serial`, and SPRT protocol parsing
|
||||
### Task 1: `SprtDiscoveryService`, test project, and `DiscoveredPrinter.Serial`
|
||||
|
||||
Creates the test project the later tasks need, adds the one new DTO field, and implements the pure functions of the SPRT protocol so they can be tested without a network.
|
||||
Delivers the complete SPRT discovery service: the test project the later tasks need, the one new DTO field, the protocol's pure functions, and the network path.
|
||||
|
||||
**Files:**
|
||||
- Create: `Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
@@ -32,8 +32,8 @@ Creates the test project the later tasks need, adds the one new DTO field, and i
|
||||
- Modify: `Inspectron.Epson.slnx`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `DiscoveredPrinter` (existing class, `Inspectron.Epson`).
|
||||
- Produces: `SprtDiscoveryService.BuildProbe()` returning `byte[]`; `SprtDiscoveryService.TryParseReply(byte[] payload, string ipAddress, out DiscoveredPrinter? printer)` returning `bool`. Task 2 uses both. `DiscoveredPrinter.Serial` (`string?`) used by Tasks 1-2.
|
||||
- Consumes: `DiscoveredPrinter`, `IDiscoveryService` (existing, `Inspectron.Epson`).
|
||||
- Produces: `SprtDiscoveryService(ILogger? logger = null)`; `SprtDiscoveryService.BuildProbe()` returning `byte[]`; `SprtDiscoveryService.TryParseReply(byte[] payload, string ipAddress, out DiscoveredPrinter? printer)` returning `bool`; a working `DiscoverPrintersAsync`. Task 6 constructs it for DI. `DiscoveredPrinter.Serial` (`string?`).
|
||||
|
||||
- [ ] **Step 1: Create the test project**
|
||||
|
||||
@@ -160,9 +160,27 @@ public class SprtDiscoveryServiceTests
|
||||
Assert.True(ok);
|
||||
Assert.Equal("", printer!.Serial);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverPrintersAsync_returns_empty_when_port_9100_is_taken()
|
||||
{
|
||||
// Hold UDP 9100 so the service cannot bind it. It must degrade, not throw:
|
||||
// ENPC discovery has to keep working when this one cannot start.
|
||||
using var blocker = new System.Net.Sockets.UdpClient();
|
||||
blocker.Client.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 9100));
|
||||
|
||||
var service = new SprtDiscoveryService();
|
||||
var result = await service.DiscoverPrintersAsync(TimeSpan.FromMilliseconds(200));
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note there is deliberately no "nothing answers" test. Asserting an empty list from
|
||||
a real broadcast would pass on a developer laptop and fail on any network with a
|
||||
POS895 on it, including the print server's own.
|
||||
|
||||
- [ ] **Step 3: Run the tests to verify they fail**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
@@ -192,7 +210,10 @@ In `Inspectron.Epson/PreconfiguredDiscoveryService.cs`, inside the object initia
|
||||
Create `Inspectron.Epson/SprtDiscoveryService.cs`:
|
||||
|
||||
```csharp
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Inspectron.Epson;
|
||||
|
||||
@@ -214,11 +235,20 @@ public class SprtDiscoveryService : IDiscoveryService
|
||||
{
|
||||
private const string ProbeMagic = "SPRT-Infor";
|
||||
private const int MacLength = 17;
|
||||
private const int DiscoveryPort = 3289;
|
||||
private const int ReplyPort = 9100;
|
||||
|
||||
/// <summary>Model reported for anything answering SPRT. The reply carries no
|
||||
/// model name, and a POS896 is indistinguishable from a POS895 here.</summary>
|
||||
public const string SprtModelName = "POS895";
|
||||
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
public SprtDiscoveryService(ILogger? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>The 13-byte discovery query, byte-identical to EasySet's.</summary>
|
||||
public static byte[] BuildProbe()
|
||||
{
|
||||
@@ -265,102 +295,6 @@ public class SprtDiscoveryService : IDiscoveryService
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<List<DiscoveredPrinter>> DiscoverPrintersAsync(
|
||||
TimeSpan? timeout = null,
|
||||
Action<DiscoveredPrinter>? onPrinterDiscovered = null)
|
||||
{
|
||||
throw new NotImplementedException("Task 2");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run the tests to verify they pass**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 6 tests.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add Inspectron.Epson.Tests Inspectron.Epson.slnx Inspectron.Epson/SprtDiscoveryService.cs Inspectron.Epson/DiscoveredPrinter.cs Inspectron.Epson/PreconfiguredDiscoveryService.cs
|
||||
git commit -m "add SPRT discovery protocol parsing and test project"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: `SprtDiscoveryService` network path
|
||||
|
||||
Broadcasts the probe and collects replies. Binding UDP 9100 can fail when another process holds it; discovery must degrade to empty rather than crash, so ENPC discovery still runs.
|
||||
|
||||
**Files:**
|
||||
- Modify: `Inspectron.Epson/SprtDiscoveryService.cs`
|
||||
- Modify: `Inspectron.Epson.Tests/SprtDiscoveryServiceTests.cs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `BuildProbe()`, `TryParseReply(...)` from Task 1.
|
||||
- Produces: `SprtDiscoveryService(ILogger? logger = null)` constructor; a working `DiscoverPrintersAsync`. Task 6 constructs it for DI.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
|
||||
Append to `Inspectron.Epson.Tests/SprtDiscoveryServiceTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public async Task DiscoverPrintersAsync_returns_empty_when_port_9100_is_taken()
|
||||
{
|
||||
// Hold UDP 9100 so the service cannot bind it. It must degrade, not throw:
|
||||
// ENPC discovery has to keep working when this one cannot start.
|
||||
using var blocker = new System.Net.Sockets.UdpClient();
|
||||
blocker.Client.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 9100));
|
||||
|
||||
var service = new SprtDiscoveryService();
|
||||
var result = await service.DiscoverPrintersAsync(TimeSpan.FromMilliseconds(200));
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DiscoverPrintersAsync_returns_empty_when_nothing_answers()
|
||||
{
|
||||
var service = new SprtDiscoveryService();
|
||||
var result = await service.DiscoverPrintersAsync(TimeSpan.FromMilliseconds(300));
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the tests to verify they fail**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj --filter DiscoverPrintersAsync`
|
||||
Expected: FAIL with `NotImplementedException: Task 2`.
|
||||
|
||||
- [ ] **Step 3: Write the implementation**
|
||||
|
||||
In `Inspectron.Epson/SprtDiscoveryService.cs`, add these usings at the top:
|
||||
|
||||
```csharp
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Microsoft.Extensions.Logging;
|
||||
```
|
||||
|
||||
Add a logger field and constructor above `BuildProbe`:
|
||||
|
||||
```csharp
|
||||
private const int DiscoveryPort = 3289;
|
||||
private const int ReplyPort = 9100;
|
||||
|
||||
private readonly ILogger? _logger;
|
||||
|
||||
public SprtDiscoveryService(ILogger? logger = null)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
```
|
||||
|
||||
Replace the `NotImplementedException` body of `DiscoverPrintersAsync` with:
|
||||
|
||||
```csharp
|
||||
public async Task<List<DiscoveredPrinter>> DiscoverPrintersAsync(
|
||||
TimeSpan? timeout = null,
|
||||
Action<DiscoveredPrinter>? onPrinterDiscovered = null)
|
||||
@@ -442,23 +376,24 @@ Replace the `NotImplementedException` body of `DiscoverPrintersAsync` with:
|
||||
|
||||
return discovered;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the tests to verify they pass**
|
||||
- [ ] **Step 6: Run the tests to verify they pass**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 8 tests.
|
||||
Expected: PASS, 7 tests.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add Inspectron.Epson/SprtDiscoveryService.cs Inspectron.Epson.Tests/SprtDiscoveryServiceTests.cs
|
||||
git commit -m "implement SPRT discovery broadcast and reply collection"
|
||||
git add Inspectron.Epson.Tests Inspectron.Epson.slnx Inspectron.Epson/SprtDiscoveryService.cs Inspectron.Epson/DiscoveredPrinter.cs Inspectron.Epson/PreconfiguredDiscoveryService.cs
|
||||
git commit -m "add SPRT discovery service for POS895 printers"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: `CompositeDiscoveryService`
|
||||
### Task 2: `CompositeDiscoveryService`
|
||||
|
||||
Runs several discovery services concurrently and merges their results, so ENPC and SPRT printers appear in one list.
|
||||
|
||||
@@ -674,7 +609,7 @@ Note the children are called **without** the `onPrinterDiscovered` callback. The
|
||||
- [ ] **Step 4: Run the tests to verify they pass**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 13 tests.
|
||||
Expected: PASS, 12 tests.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
@@ -685,7 +620,7 @@ git commit -m "add CompositeDiscoveryService merging ENPC and SPRT results"
|
||||
|
||||
---
|
||||
|
||||
### Task 4: `POS895` printer adapter
|
||||
### Task 3: `POS895` printer adapter
|
||||
|
||||
The adapter differs from `TM_T30IIITranslated` in exactly three ways, each confirmed on hardware. A recording fake asserts the two that would otherwise fail silently on paper.
|
||||
|
||||
@@ -696,7 +631,7 @@ The adapter differs from `TM_T30IIITranslated` in exactly three ways, each confi
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `IPrinter` (`Inspectron.Epson.PrintServer.PrintServices`), `IEpsonPrinter` (`Inspectron.Epson`), `PrintCommand` (`Inspectron.Epson.PrintServer.Printers.Utils`).
|
||||
- Produces: `POS895(IEpsonPrinter printer)` with a `bool SkipDelays { get; set; }`. Task 5 constructs it.
|
||||
- Produces: `POS895(IEpsonPrinter printer)` with a `bool SkipDelays { get; set; }`. Task 4 constructs it.
|
||||
|
||||
- [ ] **Step 1: Write the recording test double**
|
||||
|
||||
@@ -1026,7 +961,7 @@ Note `PrintAsync` feeds 10 lines at the end **and** 10 on an `IsCut` command, ma
|
||||
- [ ] **Step 5: Run the tests to verify they pass**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 20 tests.
|
||||
Expected: PASS, 19 tests.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
@@ -1037,7 +972,7 @@ git commit -m "add POS895 printer adapter with LF feed and ESC * logo"
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Register printer ID `0x20` in the three switches
|
||||
### Task 4: Register printer ID `0x20` in the three switches
|
||||
|
||||
Three one-line additions in three files. The third is the one that fails silently if missed.
|
||||
|
||||
@@ -1048,7 +983,7 @@ Three one-line additions in three files. The third is the one that fails silentl
|
||||
- Create: `Inspectron.Epson.Tests/PrinterRegistrationTests.cs`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `POS895` from Task 4.
|
||||
- Consumes: `POS895` from Task 3.
|
||||
- Produces: nothing new; wires `0x20` into existing dispatch.
|
||||
|
||||
- [ ] **Step 1: Write the failing tests**
|
||||
@@ -1184,7 +1119,7 @@ Leave `EReceiptType.NextCourse` at `33, 20`. It is hardcoded for every printer a
|
||||
- [ ] **Step 6: Run the tests to verify they pass**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 25 tests.
|
||||
Expected: PASS, 24 tests.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
@@ -1195,7 +1130,7 @@ git commit -m "register printer id 0x20 as POS895 across the three dispatch poin
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Guard the nullable printer ID
|
||||
### Task 5: Guard the nullable printer ID
|
||||
|
||||
`printerId` is a `byte?` dereferenced with `.Value` twice. A printer that does not answer `GS I 1` throws `NullReferenceException` into the generic catch, reporting `PrintErrorType.Other` and burning three `PrinterQueue` retries with no usable log line.
|
||||
|
||||
@@ -1230,7 +1165,7 @@ Expected: `0 Error(s)`, and no new warnings attributable to `EpsonPrintService.c
|
||||
- [ ] **Step 3: Run the full test suite**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 25 tests.
|
||||
Expected: PASS, 24 tests.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
@@ -1241,7 +1176,7 @@ git commit -m "fail cleanly when a printer does not answer GS I 1"
|
||||
|
||||
---
|
||||
|
||||
### Task 7: DI wiring and emulation entry
|
||||
### Task 6: DI wiring and emulation entry
|
||||
|
||||
Binds the composite discovery in production and adds a 576px emulated POS895 so receipt layouts can be checked without hardware.
|
||||
|
||||
@@ -1249,7 +1184,7 @@ Binds the composite discovery in production and adds a 576px emulated POS895 so
|
||||
- Modify: `EpsonPrintService/PrintServerBootstrapper.cs:52-78`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `SprtDiscoveryService` (Task 2), `CompositeDiscoveryService` (Task 3).
|
||||
- Consumes: `SprtDiscoveryService` (Task 1), `CompositeDiscoveryService` (Task 2).
|
||||
- Produces: nothing new.
|
||||
|
||||
- [ ] **Step 1: Add the emulated POS895**
|
||||
@@ -1313,7 +1248,7 @@ Expected: `0 Error(s)`.
|
||||
- [ ] **Step 4: Run the full test suite**
|
||||
|
||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||
Expected: PASS, 25 tests.
|
||||
Expected: PASS, 24 tests.
|
||||
|
||||
- [ ] **Step 5: Verify discovery against the real printer**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user