using System.Collections.Generic; using System.Diagnostics; using System.Text; using System.Threading.Tasks; using FluentAssertions; using foodmarket.Pos.Core.Hardware; using Xunit; using PortKind = foodmarket.Pos.Core.Hardware.HardwareProbe.PortKind; using PortDesc = foodmarket.Pos.Core.Hardware.HardwareProbe.PortDesc; public class ScaleAutodiscoveryTests { private static byte[] Frame(string payload, byte control = 0x71) { var body = new List { MassaK.SOH, MassaK.STX }; body.AddRange(Encoding.ASCII.GetBytes(payload)); body.Add(control); body.Add(MassaK.ETX); body.Add(MassaK.EOT); return body.ToArray(); } // Требование 5: «зависающий» порт не должен задерживать поиск дольше таймаута; // живой порт вторым — должен быть найден. [Fact] public async Task Hanging_port_does_not_block_discovery_of_live_port() { var ports = new List { new("COMHANG", PortKind.Unknown, "зависает на Open"), new("COMLIVE", PortKind.Unknown, "живой"), }; var live = Frame("S 1.235kg"); foodmarket.Pos.Core.Hardware.ISerialTransport Make(string p, int b) => p == "COMHANG" ? new foodmarket.UnitTests.FakeScaleTransport { OpenDelayMs = 30000 } : new foodmarket.UnitTests.FakeScaleTransport { AnswerAck = true, Frame = live }; var sw = Stopwatch.StartNew(); var res = await ScaleAutodiscovery.DiscoverAsync( log: null, transportFactory: Make, portsOverride: ports, perPortMs: 1500, maxParallel: 4, budgetMs: 15000); sw.Stop(); res.Device.Should().NotBeNull(); res.Device!.Port.Should().Be("COMLIVE"); res.Device.SampleKg.Should().Be(1.235m); sw.ElapsedMilliseconds.Should().BeLessThan(5000); // не ждём 30 с зависшего порта } // Требование 2/3: USB-serial проверяется раньше Bluetooth — находим весы на CH340, // не трогая Bluetooth-порт первым. [Fact] public async Task UsbSerial_tier_is_probed_before_bluetooth() { var ports = new List { new("COM7", PortKind.Bluetooth, "Bluetooth"), new("COM22", PortKind.UsbSerial, "USB-SERIAL CH340"), }; var btTouched = false; var live = Frame("S 0.000kg"); foodmarket.Pos.Core.Hardware.ISerialTransport Make(string p, int b) { if (p == "COM7") { btTouched = true; return new foodmarket.UnitTests.FakeScaleTransport { OpenDelayMs = 30000 }; } return new foodmarket.UnitTests.FakeScaleTransport { AnswerAck = true, Frame = live }; } var res = await ScaleAutodiscovery.DiscoverAsync(transportFactory: Make, portsOverride: ports); res.Device!.Port.Should().Be("COM22"); btTouched.Should().BeFalse("USB-serial найден в первом тире — до Bluetooth дело не дошло"); } [Fact] public async Task No_device_reports_cleanly() { var ports = new List { new("COMX", PortKind.Unknown, "пусто") }; foodmarket.Pos.Core.Hardware.ISerialTransport Make(string p, int b) => new foodmarket.UnitTests.FakeScaleTransport { AnswerAck = false }; var res = await ScaleAutodiscovery.DiscoverAsync(transportFactory: Make, portsOverride: ports, perPortMs: 400); res.Device.Should().BeNull(); } }