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:**
|
**Files:**
|
||||||
- Create: `Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
- 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`
|
- Modify: `Inspectron.Epson.slnx`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Consumes: `DiscoveredPrinter` (existing class, `Inspectron.Epson`).
|
- Consumes: `DiscoveredPrinter`, `IDiscoveryService` (existing, `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.
|
- 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**
|
- [ ] **Step 1: Create the test project**
|
||||||
|
|
||||||
@@ -160,9 +160,27 @@ public class SprtDiscoveryServiceTests
|
|||||||
Assert.True(ok);
|
Assert.True(ok);
|
||||||
Assert.Equal("", printer!.Serial);
|
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**
|
- [ ] **Step 3: Run the tests to verify they fail**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
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`:
|
Create `Inspectron.Epson/SprtDiscoveryService.cs`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Inspectron.Epson;
|
namespace Inspectron.Epson;
|
||||||
|
|
||||||
@@ -214,11 +235,20 @@ public class SprtDiscoveryService : IDiscoveryService
|
|||||||
{
|
{
|
||||||
private const string ProbeMagic = "SPRT-Infor";
|
private const string ProbeMagic = "SPRT-Infor";
|
||||||
private const int MacLength = 17;
|
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
|
/// <summary>Model reported for anything answering SPRT. The reply carries no
|
||||||
/// model name, and a POS896 is indistinguishable from a POS895 here.</summary>
|
/// model name, and a POS896 is indistinguishable from a POS895 here.</summary>
|
||||||
public const string SprtModelName = "POS895";
|
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>
|
/// <summary>The 13-byte discovery query, byte-identical to EasySet's.</summary>
|
||||||
public static byte[] BuildProbe()
|
public static byte[] BuildProbe()
|
||||||
{
|
{
|
||||||
@@ -265,102 +295,6 @@ public class SprtDiscoveryService : IDiscoveryService
|
|||||||
return true;
|
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(
|
public async Task<List<DiscoveredPrinter>> DiscoverPrintersAsync(
|
||||||
TimeSpan? timeout = null,
|
TimeSpan? timeout = null,
|
||||||
Action<DiscoveredPrinter>? onPrinterDiscovered = null)
|
Action<DiscoveredPrinter>? onPrinterDiscovered = null)
|
||||||
@@ -442,23 +376,24 @@ Replace the `NotImplementedException` body of `DiscoverPrintersAsync` with:
|
|||||||
|
|
||||||
return discovered;
|
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`
|
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
|
```bash
|
||||||
git add Inspectron.Epson/SprtDiscoveryService.cs Inspectron.Epson.Tests/SprtDiscoveryServiceTests.cs
|
git add Inspectron.Epson.Tests Inspectron.Epson.slnx Inspectron.Epson/SprtDiscoveryService.cs Inspectron.Epson/DiscoveredPrinter.cs Inspectron.Epson/PreconfiguredDiscoveryService.cs
|
||||||
git commit -m "implement SPRT discovery broadcast and reply collection"
|
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.
|
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**
|
- [ ] **Step 4: Run the tests to verify they pass**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||||
Expected: PASS, 13 tests.
|
Expected: PASS, 12 tests.
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **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.
|
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:**
|
**Interfaces:**
|
||||||
- Consumes: `IPrinter` (`Inspectron.Epson.PrintServer.PrintServices`), `IEpsonPrinter` (`Inspectron.Epson`), `PrintCommand` (`Inspectron.Epson.PrintServer.Printers.Utils`).
|
- 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**
|
- [ ] **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**
|
- [ ] **Step 5: Run the tests to verify they pass**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||||
Expected: PASS, 20 tests.
|
Expected: PASS, 19 tests.
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [ ] **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.
|
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`
|
- Create: `Inspectron.Epson.Tests/PrinterRegistrationTests.cs`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Consumes: `POS895` from Task 4.
|
- Consumes: `POS895` from Task 3.
|
||||||
- Produces: nothing new; wires `0x20` into existing dispatch.
|
- Produces: nothing new; wires `0x20` into existing dispatch.
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing tests**
|
- [ ] **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**
|
- [ ] **Step 6: Run the tests to verify they pass**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||||
Expected: PASS, 25 tests.
|
Expected: PASS, 24 tests.
|
||||||
|
|
||||||
- [ ] **Step 7: Commit**
|
- [ ] **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.
|
`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**
|
- [ ] **Step 3: Run the full test suite**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
||||||
Expected: PASS, 25 tests.
|
Expected: PASS, 24 tests.
|
||||||
|
|
||||||
- [ ] **Step 4: Commit**
|
- [ ] **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.
|
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`
|
- Modify: `EpsonPrintService/PrintServerBootstrapper.cs:52-78`
|
||||||
|
|
||||||
**Interfaces:**
|
**Interfaces:**
|
||||||
- Consumes: `SprtDiscoveryService` (Task 2), `CompositeDiscoveryService` (Task 3).
|
- Consumes: `SprtDiscoveryService` (Task 1), `CompositeDiscoveryService` (Task 2).
|
||||||
- Produces: nothing new.
|
- Produces: nothing new.
|
||||||
|
|
||||||
- [ ] **Step 1: Add the emulated POS895**
|
- [ ] **Step 1: Add the emulated POS895**
|
||||||
@@ -1313,7 +1248,7 @@ Expected: `0 Error(s)`.
|
|||||||
- [ ] **Step 4: Run the full test suite**
|
- [ ] **Step 4: Run the full test suite**
|
||||||
|
|
||||||
Run: `dotnet test Inspectron.Epson.Tests/Inspectron.Epson.Tests.csproj`
|
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**
|
- [ ] **Step 5: Verify discovery against the real printer**
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user