Доступные DTO для оффлайн-кассы (food-market.pos): • ProductSyncDto/PriceSyncDto/StockSyncDto/CounterpartySyncDto — выгрузка изменений для последующей пробивки; • PosSyncResponse — конверт всего sync-ответа с ServerTime (reference time против клок-дрейфа кассы) и DeletedProductIds; • PosSaleDto/PosSaleLineDto/PosSaleBatchDto — батч продаж от кассы. PosSaleBatchDto несёт IdempotencyKey + каждая продажа имеет ClientSaleId (двойная идемпотентность); • PosSaleBatchResponse — Accepted/Failed + ReplayedFromCache флаг. Версионирование на уровне namespace — для v2 будет рядом без breaking changes. Required-поля везде where applicable: компилятор обяжет заполнить новые обязательные поля при появлении v1.X добавок. Тесты: 3 unit на сериализационный round-trip (компиляция падёт при удалении любого поля контракта — это и есть тест public API). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
2.8 KiB
C#
83 lines
2.8 KiB
C#
using System.Text.Json;
|
||
using FluentAssertions;
|
||
using foodmarket.Shared.Pos.V1;
|
||
using Xunit;
|
||
|
||
namespace foodmarket.UnitTests;
|
||
|
||
/// <summary>Контракт POS — public API: ломая его, мы ломаем все Windows-кассы
|
||
/// в поле. Тестируем сериализацию round-trip + наличие required-полей.
|
||
/// При попытке убрать поле или поменять тип компиляция этих тестов упадёт.</summary>
|
||
public class PosContractsTests
|
||
{
|
||
[Fact]
|
||
public void Product_sync_dto_round_trips()
|
||
{
|
||
var dto = new ProductSyncDto
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
Name = "Молоко 1л",
|
||
Article = "M-001",
|
||
Barcodes = new[] { "1234567890123", "2345678901234" },
|
||
UnitCode = "л",
|
||
Packaging = 1,
|
||
VatPercent = 12m,
|
||
VatEnabled = true,
|
||
IsMarked = false,
|
||
IsArchived = false,
|
||
UpdatedAt = DateTime.UtcNow,
|
||
};
|
||
var json = JsonSerializer.Serialize(dto);
|
||
var back = JsonSerializer.Deserialize<ProductSyncDto>(json)!;
|
||
back.Should().BeEquivalentTo(dto);
|
||
}
|
||
|
||
[Fact]
|
||
public void Batch_dto_carries_idempotency_key()
|
||
{
|
||
var batch = new PosSaleBatchDto
|
||
{
|
||
IdempotencyKey = Guid.NewGuid(),
|
||
Sales = new[]
|
||
{
|
||
new PosSaleDto
|
||
{
|
||
ClientSaleId = Guid.NewGuid(),
|
||
OccurredAt = DateTime.UtcNow,
|
||
Payment = 0, PaidCash = 1000m, PaidCard = 0m,
|
||
Lines = new[]
|
||
{
|
||
new PosSaleLineDto
|
||
{
|
||
ProductId = Guid.NewGuid(), Quantity = 1m,
|
||
UnitPrice = 1000m, Discount = 0m, VatPercent = 12m,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
};
|
||
var json = JsonSerializer.Serialize(batch);
|
||
var back = JsonSerializer.Deserialize<PosSaleBatchDto>(json)!;
|
||
back.IdempotencyKey.Should().Be(batch.IdempotencyKey);
|
||
back.Sales.Should().HaveCount(1);
|
||
back.Sales[0].Lines.Should().HaveCount(1);
|
||
}
|
||
|
||
[Fact]
|
||
public void Sync_response_collects_all_groups()
|
||
{
|
||
var resp = new PosSyncResponse
|
||
{
|
||
ServerTime = DateTime.UtcNow,
|
||
Products = Array.Empty<ProductSyncDto>(),
|
||
Prices = Array.Empty<PriceSyncDto>(),
|
||
Stocks = Array.Empty<StockSyncDto>(),
|
||
Counterparties = Array.Empty<CounterpartySyncDto>(),
|
||
DeletedProductIds = Array.Empty<Guid>(),
|
||
};
|
||
var json = JsonSerializer.Serialize(resp);
|
||
var back = JsonSerializer.Deserialize<PosSyncResponse>(json)!;
|
||
back.Should().BeEquivalentTo(resp);
|
||
}
|
||
}
|