food-market/tests/food-market.UnitTests/PagedRequestTests.cs
nns 9588d03bf4 test(s15): axe a11y + focus traps + unit coverage 80% + property tests + backup drill
Sprint 15 финальный — реальные axe + coverage + pg_restore numbers.

Ключевые цифры:
- axe-core: critical=0 on 10 страниц stage'а; serious 12→9
  после фиксов (sidebar contrast + 8 icon-only back-arrow aria-labels).
- Unit coverage: Application 56%→83%, Domain 11%→79%, combined
  60%→80%. Тестов 68→147 (+79).
- Backup recovery drill: RTO ~25 секунд end-to-end
  (pg_dump 2s + pg_restore 4s + dotnet startup 19s).

Что сделано:
1. @axe-core/playwright + stage-ui-15 (10 страниц) + stage-ui-16
   (SR smoke на login: getByLabel, role=alert, aria-describedby,
   keyboard nav).
2. useFocusTrap hook (WCAG 2.4.3 + 2.1.2): return-focus, mount-focus,
   Tab cycle. Подключён к Modal + ConfirmDialog с opt-in
   defaultFocus='cancel'|'confirm'. ConfirmDialog по дефолту фокусит
   Cancel для destructive actions (safer чем Enter→Delete).
3. A11y фиксы:
   • text-slate-400→text-slate-500 в sidebar (contrast 2.63→4.61).
   • 8 страниц edit с back-arrow Link — aria-label + aria-hidden
     на иконке + текст-slate-500 цвет.
   • Modal close button — то же.
   • LoginPage — aria-invalid/aria-describedby/role=alert на
     ошибках валидации.
   • Field component — role="alert" на error span (announce'ит SR).
4. 8 файлов unit-тестов: PhoneNormalization, PagedRequest,
   RequiredGuid, RolePermissions (Domain), DomainPocoSmoke,
   DomainFullPropertyTouch, CatalogDtosSmoke, StockServiceProperty
   (4 seeds × 4 size + batch + 2-product isolation).
5. Backup-drill: pg_dump со stage'а → fresh postgres:16-alpine →
   pg_restore → dotnet run против восстановленной БД → /health/ready
   Healthy. Команды и timing в RUNBOOK.md.
6. Docs review:
   • MULTI-TENANCY чеклист «добавить tenant-сущность» расширен с 6
     до 19 шагов (Domain → EF Config → Migration с Xmin →
     RolePermissions → Validation → Controller + RequiresPermission →
     Audit + SensitiveOpsAudit → property tests).
   • ARCHITECTURE.md — Sprint 13-15 changes таблица.
   • DEVELOPER-GUIDE.md — «что добавилось после первого guide'а» +
     a11y pitfalls в «что НЕ делать».

Stage smoke ✓. Это финальный автономно-безопасный спринт. Дальше
нужен вход от user'а (ОФД keys, MoySklad tokens, Windows для POS,
прод-деплой план, kz-перевод, реальный SMTP).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:53:38 +05:00

65 lines
2.3 KiB
C#
Raw Permalink 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 FluentAssertions;
using foodmarket.Application.Common;
using Xunit;
namespace foodmarket.UnitTests;
/// <summary>PagedRequest / PagedResult — простые helper'ы, но Skip/Take/Desc/
/// TotalPages нетривиальны на edge-кейсах. Sprint 15 — расширение покрытия.</summary>
public class PagedRequestTests
{
[Theory]
[InlineData(1, 50, 0)] // первая страница → skip 0
[InlineData(2, 50, 50)] // вторая → skip 50
[InlineData(5, 20, 80)] // 5×20 = 80
[InlineData(0, 50, 0)] // невалидная страница ≤0 → skip 0 (Max)
public void Skip_calculated_from_Page_and_PageSize(int page, int pageSize, int expectedSkip)
=> new PagedRequest { Page = page, PageSize = pageSize }.Skip.Should().Be(expectedSkip);
[Theory]
[InlineData(50, 50)]
[InlineData(1, 1)]
[InlineData(501, 500)] // clamp на 500 сверху
[InlineData(0, 1)] // clamp на 1 снизу
[InlineData(-1, 1)] // отрицательные тоже клампим
public void Take_clamps_to_1_500(int requested, int expected)
=> new PagedRequest { PageSize = requested }.Take.Should().Be(expected);
[Theory]
[InlineData("desc", true)]
[InlineData("DESC", true)]
[InlineData("asc", false)]
[InlineData(null, false)]
[InlineData("", false)]
[InlineData("garbage", false)]
public void Desc_only_for_explicit_desc(string? order, bool expected)
=> new PagedRequest { Order = order }.Desc.Should().Be(expected);
[Theory]
[InlineData(100, 50, 2)]
[InlineData(101, 50, 3)]
[InlineData(0, 50, 0)]
[InlineData(7, 50, 1)]
public void TotalPages_calculated_from_Total_and_PageSize(int total, int pageSize, int expected)
{
var result = new PagedResult<string>
{
Items = new List<string>(),
Total = total,
Page = 1,
PageSize = pageSize,
};
result.TotalPages.Should().Be(expected);
}
[Fact]
public void Default_Page_and_PageSize_sensible()
{
var r = new PagedRequest();
r.Page.Should().Be(1);
r.PageSize.Should().Be(50);
r.Skip.Should().Be(0);
r.Take.Should().Be(50);
}
}