food-market/tests/food-market.UnitTests/ScaleAutodiscoveryTests.cs
nns f15a12695c fix(pos): автопоиск весов не виснет на Bluetooth-портах (реальный баг на кассе 11.10)
На ноутбуке магазина автопоиск спотыкался о Bluetooth-порты (COM7/COM8 — Open/Write
висят), выбирал по умолчанию BT и не доходил до COM22 (USB-SERIAL CH340, где весы).
Парсер/протокол исправны (ручной выбор COM22 57600 работает) — баг только в поиске.

- ScaleAutodiscovery.DiscoverAsync: тирами по типу порта — USB-serial первыми,
  неопознанные в середине, Bluetooth последними; на каждый порт ЖЁСТКИЙ таймаут
  (Open обёрнут в задачу с гонкой против Task.Delay — SerialPort.Open на BT виснет);
  порты перебираются параллельно (до 4), общий бюджет ~15 с; любая ошибка/таймаут →
  следующий порт; по каждому порту строка в лог («COM22 CH340: ENQ→ACK, вес 0.000 кг»,
  «COM7 Bluetooth: пропущен», «COM15: нет ответа»).
- HardwareProbe: Classify (CH340/Prolific/FTDI/CP210x → USB-serial; BTHENUM/Bluetooth
  → Bluetooth; ttyUSB/ttyACM → USB-serial), ClassifyPorts (без открытия портов),
  OpenCloseBounded (жёсткий таймаут — листинг не виснет на BT). ListComPorts не
  открывает Bluetooth-порты.
- HwCheck: по умолчанию выбирается НЕ Bluetooth-порт; окно показывает журнал
  автопоиска по портам; список портов помечает [USB-serial]/[Bluetooth].
- Основная касса (экран «Оборудование») — та же DiscoverScaleAsync; --probe-hw не
  открывает BT-порты.
- Из zip hardware-check убран ярлык «(с тестовой печатью).bat» (печать — отдельно
  по слову владельца).
- Тесты: зависающий порт (Open 30 с) не задерживает поиск > таймаута, живой порт
  вторым — найден; USB-serial проверяется раньше Bluetooth (BT не трогаем). 53/53.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-11 09:03:45 +05:00

80 lines
3.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<byte> { 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<PortDesc>
{
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<PortDesc>
{
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<PortDesc> { 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();
}
}