1. Strengthen POS895 IsCut test: add RecordingEpsonPrinter.CallOrder so the test can assert feeds happen BEFORE the cut, plus a LineFeedBytes count assertion. The prior test could not detect the FeedAsync call being deleted from the IsCut branch (verified: test now fails when that line is removed, passes when restored). 2. CompositeDiscoveryService: distinguish "some children failed" from "every child failed". Per-child failures are still absorbed, but if every discovery service throws, the composite now rethrows instead of silently returning an empty list. Previously a total discovery failure (e.g. a routeless interface) looked like "discovery succeeded, found nothing" to PrinterDiscoveryBackgroundTask, which would expire and unregister every known printer after the retention window, discarding their queued jobs. Also corrected a doc comment that overstated Task.WhenAll's timeout guarantee. 3. Update CLAUDE.md to describe the POS895/POS896 work as shipped rather than future work: added the Inspectron.Epson.Tests and Pos895Test projects, a dotnet test command, SprtDiscoveryService/ CompositeDiscoveryService/POS895 in the architecture sections, the 0x20 printer-ID note, and the established SPRT hardware facts. Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
105 lines
4.6 KiB
C#
105 lines
4.6 KiB
C#
using Inspectron.Epson;
|
|
using SixLabors.ImageSharp;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
|
|
namespace Inspectron.Epson.Tests;
|
|
|
|
/// <summary>
|
|
/// Records what an IPrinter adapter sends. Only the calls the POS895 adapter
|
|
/// makes are recorded; the rest satisfy the interface and do nothing.
|
|
/// </summary>
|
|
public sealed class RecordingEpsonPrinter : IEpsonPrinter
|
|
{
|
|
public List<byte[]> RawCommands { get; } = new();
|
|
public List<int> FeedLineCalls { get; } = new();
|
|
public List<string> Text { get; } = new();
|
|
public List<(string Path, EpsonCommands.BitImageMode Mode, int MaxWidth)> BitModeImages { get; } = new();
|
|
public List<(string Path, int MaxWidth)> RasterImages { get; } = new();
|
|
public List<int> AbsolutePositions { get; } = new();
|
|
public int CutCount { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Ordered log of the calls that affect receipt layout, so a test can assert
|
|
/// that feeds happen BEFORE a cut rather than merely that both occurred.
|
|
/// </summary>
|
|
public List<string> CallOrder { get; } = new();
|
|
|
|
/// <summary>Total LF bytes across every raw command sent.</summary>
|
|
public int LineFeedBytes => RawCommands.Sum(c => c.Count(b => b == 0x0A));
|
|
|
|
public bool IsConnected => true;
|
|
|
|
public Task SendRawCommandAsync(byte[] command)
|
|
{
|
|
RawCommands.Add(command);
|
|
CallOrder.Add($"raw:{command.Length}");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task FeedLinesAsync(int lines)
|
|
{
|
|
FeedLineCalls.Add(lines);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task PrintTextAsync(string text)
|
|
{
|
|
Text.Add(text);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task PrintImageBitModeAsync(string imagePath, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.SingleDensity8Dot, int maxWidth = 10)
|
|
{
|
|
BitModeImages.Add((imagePath, mode, maxWidth));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task LoadImageAsync(string imagePath, int maxWidth = 384)
|
|
{
|
|
RasterImages.Add((imagePath, maxWidth));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task SetAbsolutePrintPosition(int x)
|
|
{
|
|
AbsolutePositions.Add(x);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task CutAsync(bool fullCut = true)
|
|
{
|
|
CutCount++;
|
|
CallOrder.Add("cut");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
// Remaining interface members: no-ops.
|
|
public Task ConnectAsync(string ip, int port = 9100, int timeoutSeconds = 10) => Task.CompletedTask;
|
|
public void Disconnect() { }
|
|
// 0x16 / 0x12 are what the POS895 actually returns when idle and ready.
|
|
public Task<PrinterStatus> GetPrinterStatusAsync() => Task.FromResult(PrinterStatus.Parse(0x16));
|
|
public Task<OfflineStatus> GetOfflineStatusAsync() => Task.FromResult(OfflineStatus.Parse(0x12));
|
|
public Task<ErrorStatus> GetErrorStatusAsync() => Task.FromResult(ErrorStatus.Parse(0x12));
|
|
public Task<PaperSensorStatus> GetPaperSensorStatusAsync() => Task.FromResult(PaperSensorStatus.Parse(0x12));
|
|
public Task<byte> GetTM220StatusAsync() => Task.FromResult((byte)0);
|
|
public Task<byte?> GetPrinterIdAsync() => Task.FromResult<byte?>(0x20);
|
|
public Task<OverallStatus> GetOverallStatusAsync() => Task.FromResult(new OverallStatus());
|
|
public Task InitAsync() => Task.CompletedTask;
|
|
public Task SetQuadrupleMode(bool enable) => Task.CompletedTask;
|
|
public Task SetRedColor(bool enable) => Task.CompletedTask;
|
|
public Task SetEmphasized(bool enable) => Task.CompletedTask;
|
|
public Task SelectFont(EpsonCommands.PrinterFont font) => Task.CompletedTask;
|
|
public Task SetBiggerFontTM220(bool biggerWidth, bool biggerHeight = false, bool secondaryFont = true) => Task.CompletedTask;
|
|
public Task SetFontSizeAsync(int width, int height) => Task.CompletedTask;
|
|
public Task PrintTextAndCutAsync(string text, int feedLines = 5) => Task.CompletedTask;
|
|
public Task PrintBuffer() => Task.CompletedTask;
|
|
public Task LoadImageAsync(Stream imageStream, int maxWidth = 384) => Task.CompletedTask;
|
|
public Task LoadImageAsync(Image<Rgba32> image, int maxWidth = 384) => Task.CompletedTask;
|
|
public Task PrintLoadedImage() => Task.CompletedTask;
|
|
public Task PrintImageBitModeAsync(Stream imageStream, EpsonCommands.BitImageMode mode = EpsonCommands.BitImageMode.DoubleDensity24Dot, int maxWidth = 384) => Task.CompletedTask;
|
|
public Task PrintImageBitModeAsync(Image<Rgba32> image, EpsonCommands.BitImageMode mode, int maxWidth) => Task.CompletedTask;
|
|
public Task SetDefaultLineSpacing() => Task.CompletedTask;
|
|
public Task SetCustomLineSpacing(int spacing = 24) => Task.CompletedTask;
|
|
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
|
}
|