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>
65 lines
2.3 KiB
C#
65 lines
2.3 KiB
C#
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);
|
||
}
|
||
}
|