«Последние продажи»/Z-отчёт показывали времена вперёд, потому что дата чека бралась от клиента (input.Date / POS OccurredAt) без нормализации: наивная локальная дата или спешащие часы кассы давали будущий инстант. Добавил BusinessTime.ToInstantUtc: Kind→UTC и clamp будущего (>5 мин) к now — чек не может быть в будущем. Применено в рознице (create) и в приёме чеков с кассы (движение берёт тот же инстант). Юнит-тесты на нормализацию/clamp. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
53 lines
2 KiB
C#
53 lines
2 KiB
C#
using FluentAssertions;
|
||
using foodmarket.Application.Common;
|
||
using Xunit;
|
||
|
||
namespace foodmarket.UnitTests;
|
||
|
||
/// <summary>Нормализация даты документа к UTC и защита от «будущих» времён
|
||
/// (корень бага «Последние продажи» показывали время вперёд).</summary>
|
||
public class BusinessTimeTests
|
||
{
|
||
[Fact]
|
||
public void Utc_date_in_past_is_unchanged()
|
||
{
|
||
var past = DateTime.UtcNow.AddHours(-3);
|
||
BusinessTime.ToInstantUtc(past).Should().BeCloseTo(past, TimeSpan.FromSeconds(1));
|
||
}
|
||
|
||
[Fact]
|
||
public void Future_date_is_clamped_to_now()
|
||
{
|
||
var future = DateTime.UtcNow.AddHours(3);
|
||
var result = BusinessTime.ToInstantUtc(future);
|
||
result.Should().BeOnOrBefore(DateTime.UtcNow.AddSeconds(1));
|
||
result.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromMinutes(1), "будущее заменяется на now");
|
||
}
|
||
|
||
[Fact]
|
||
public void Small_clock_skew_within_tolerance_is_kept()
|
||
{
|
||
var slightlyAhead = DateTime.UtcNow.AddMinutes(2);
|
||
BusinessTime.ToInstantUtc(slightlyAhead).Should().BeCloseTo(slightlyAhead, TimeSpan.FromSeconds(1),
|
||
"небольшой разбег часов (до 5 мин) допускаем");
|
||
}
|
||
|
||
[Fact]
|
||
public void Local_kind_is_converted_to_utc()
|
||
{
|
||
var local = DateTime.SpecifyKind(DateTime.Now.AddHours(-1), DateTimeKind.Local);
|
||
var result = BusinessTime.ToInstantUtc(local);
|
||
result.Kind.Should().Be(DateTimeKind.Utc);
|
||
result.Should().BeCloseTo(local.ToUniversalTime(), TimeSpan.FromSeconds(1));
|
||
}
|
||
|
||
[Fact]
|
||
public void Unspecified_kind_is_treated_as_utc_no_shift()
|
||
{
|
||
var naive = DateTime.SpecifyKind(DateTime.UtcNow.AddHours(-2), DateTimeKind.Unspecified);
|
||
var result = BusinessTime.ToInstantUtc(naive);
|
||
result.Kind.Should().Be(DateTimeKind.Utc);
|
||
result.Should().BeCloseTo(DateTime.SpecifyKind(naive, DateTimeKind.Utc), TimeSpan.FromSeconds(1));
|
||
}
|
||
}
|