# Проверки и требования к тестируемости > Редакция 13.10.2026 по дополнению NEW-01–09 и реализации прототипа `5213150`. Расчётное поведение ниже принято для прототипа; инфраструктура, авторизация, интеграции и хранение рабочего сервиса остаются предложениями. Общая QA всех NEW ещё предстоит. [Расчётный контракт](calculation-contract.md), [матрица покрытия](addendum-coverage.md). Это план будущих тестов сервиса, не отчёт об их выполнении. В STT-232 проверяются документы и рендер схем; исходного кода сервиса нет. REQ из spec.md включается в имя каждого проверяющего теста, например `[REQ-004] два параллельных решения не превышают дневную загрузку`. ## Уровни Время — бюджет обратной связи на типичном CI runner, а не измерение и не SLA. Если превышено, искать медленные фикстуры/интеграции, не выкидывать проверки прав и расчётов. | Уровень | Что проверяет | Что настоящее / что подменяется | Инструмент | Где и ориентир | | --- | --- | --- | --- | --- | | L1 домен | Формулы §1, finite-state Request/Assignment, C>R, FTE, дни изменения, null/0, округление | Чистые функции; календарь, Clock, ID и ставки — значения/фейки; БД/сети нет | vitest + fast-check, таблицы AC и property-based | Локально/каждый CI, до 15 с | | L2 модуль | Публичные интерфейсы шести модулей, транзакции, FK/unique, idempotency, rollback, scope, снимки | Реальная PGlite in-memory, схема из тех же миграций Drizzle; fake внешние порты | vitest, отдельная БД на тест | Локально/каждый CI, до 60 с | | L3 контракт адаптеров | Bitrix/Time/1С/IdentityProvider: parse, next/cursor, 200+error, timeout, 429, исправления, деактивация, missing fields | Реальный HTTP-адаптер, локальный stub HTTP, записанные обезличенные ответы, без credentials | vitest, фикстуры JSON и HTTP stub | Локально/каждый CI, до 30 с; opt-in STT-27 отдельно до 2 мин | | L4 API | Коды/DTO/zod, CSRF, версия, роль×эндпоинт, field redaction, pagination | Настоящий Nitro в тесте, PGlite, fake источники и test identity | vitest + @nuxt/test-utils, HTTP запросы | Локально/каждый CI, до 90 с | | L5 e2e | 5 сценариев ниже, формы и запросы, состояние после сбоя | Собранный test-вариант приложения, сценарные seeds, fake HTTP Sphere/Time/1С | Playwright | Локально/CI, до 3 мин | | Компоненты | Только сложная таблица плана и ввод FTE по месяцам: клавиатура, сохранение совпавших месяцев, явный 0, раскрытие | Vue-компонент и composable; API fake; расчёты отдельно L1 | vitest + @vue/test-utils | Локально/CI, до 20 с | | Миграции | Пустая БД и snapshot предыдущей схемы с заявками/историей/частичным фактом; неизменность сумм/связей | Реальные PGlite и Postgres, все SQL migrations | vitest + Drizzle migrator, CI Postgres service | CI и локально по флагу, до 90 с | | Postgres concurrency | Два соединения: решения одному сотруднику, два pending-change, lease/fencing, конфликт sync/rate/approval, rollback журнала | Настоящий Postgres, те же модули; внешние порты fake | vitest и CI Postgres service | Каждый CI, локально по DATABASE_URL, до 60 с | | Production safety | Fake-login отсутствует в prod build даже с SPHERE_FAKE/TIME_FAKE, секреты отсутствуют в JS/ошибках, readiness требует Postgres | Production build + Postgres; no live external credentials | HTTP smoke через vitest/Node, проверка bundle | Каждый CI, до 45 с без времени сборки | PGlite — настоящая embedded БД для модульных тестов, но не доказательство конкурентности Postgres. Нельзя проверять race простым Promise.all в одном PGlite connection. Для Postgres тест управляет двумя соединениями и барьерами: обе транзакции стартовали, одна ждёт lock; результат ровно одно успешное решение, второе получает OVERLOAD/конфликт, а итог ≤1 FTE. ## Сценарии и инварианты L1: воспроизвести AC 41–66 из ТЗ и соответствующие createExamples из seed.js, ноябрьский пример FR 24 на производственном календаре: 0,5 FTE с 16.11.2026 → 35,5 ч при H=159 и нулевых отсутствиях. Проверить рабочую субботу 20.02.2027 на 7 ч и недельную границу января–марта 2026 без нормы 2025. Reference calc.js используется для сравнительных примеров, но тесты не должны копировать реализацию и подтверждать её ошибку. Property-based, минимум 1000 примеров для ядра в CI, seed и минимальный counterexample печатаются при падении: - REQ-008/015: план до effectiveDate неизменен; release после даты=0; reduce не увеличивает ни один день; сумма часов разбиения месяца равна целому; days=0 не заменяется месячным FTE. - REQ-004: принятый набор назначений не превышает 1+допуск ни в один рабочий день; перестановка заявок/строк не меняет результат проверки уже фиксированного плана. - REQ-009/010: сумма доходов частей равна доходу целого до финального округления; взвешенная ставка при положительных весах находится между min/max; нулевые часы не влияют на среднее; неизвестная активная ставка делает результат неизвестным. - REQ-026/027: накопительная утилизация = сумма числителей / сумма знаменателей; V не входит в A; при 0 знаменателя нет Infinity/NaN; замена missing на 0 меняет полноту, а не часы известных строк. - REQ-031/035: повтор одного snapshot не меняет число сущностей и сумму A; новая версия записи заменяет вклад, deletion убирает только его; поломанную последнюю страницу нельзя активировать как полный снимок. L2 проверяет модуль через интерфейс, не приватные функции: сбой вставки DecisionLog откатывает approve; повтор commandId не добавляет журнал; команду с другим hash отвергает; stale request/assignment/rate версии не теряют данные. Синк деактивации не удаляет Assignment/DecisionLog и отзывает Session. ## Матрица L4: генерируется целиком Каждый маршрут из architecture.md обязан попасть в таблицу тест-кейсов; CI сравнивает реестр маршрутов/операций с матрицей, а не полагается на её ручную полноту. Роли теста: anonymous, pm-owner, pm-same-project-other-manager, pm-foreign-project, rm, operator-no-business-grants, revoked. Для каждой — чтение/запись и попытка подмены ID/filter/body. РМ и operator независимы. | Операция | anonymous | pm-owner | pm другого РП/проекта | rm | operator без бизнес-прав | | --- | --- | --- | --- | --- | --- | | requests list/get, assignments, plan | 401 | 200 в своём scope | Список без чужих; get чужого 404 | 200 весь пул | 403 | | create request, preview/change assignment | 401 | 201/200 по правилам | 404 чужой объект/проект; подмена managerId не расширяет scope | 201/200 | 403 | | approve/return/reject | 401 | 403 для своей заявки | 404 если чужая, иначе 403 | 200 или доменная ошибка | 403 | | reports JSON/CSV | 401 | 200 scoped, C и производные скрыты без viewCost | Чужие строки/итоги отсутствуют | 200, поля согласно viewCost | 403 | | patch report-data | 401 | 403 | 403 | 200 только локальные поля | 403 | | sync status / enqueue | 401 | 403/403 | 403/403 | 200/403 без operator | 200/202 | | auth begin/callback/logout | Redirect либо отказ callback | Одноразовый state, logout revokes | Те же правила сессии | Те же правила сессии | Те же правила сессии | | health live/ready | Только минимальное состояние 200/503 | То же | То же | То же | То же | Revoked всегда 401 на бизнес-API. Для rm без viewCost чтение маскируется; approve, требующий недоступной финансовой информации, запрещён до решения Q-03. Для JSON, CSV, before/after, totals и ошибок проверяется отсутствие C, plannedCost, costExcess, rateDifference и других производных скрытой C, а не только поле costRate. Недоступный проект не раскрывается счётчиком страниц. ## L5: только ключевые сценарии 1. РП создаёт две строки → РМ назначает и соглашается → план обновлён, журнал содержит реального автора, другой РП не видит заявку. 2. РП предлагает замену с даты → план до решения прежний → РМ подтверждает превышение → дневной план/освобождение/история изменены один раз. 3. РМ открывает отчёт с неполным фактом/отпуском/больничным → прочерки → fake Time полный снимок и отпуск=больничный=0 → правильные итоги/CSV; РП видит только свою область. 4. Два браузерных контекста редактируют одну заявку → второй получает 409, его ввод сохранён; повтор первого commandId не дублирует решение. 5. Fake источник становится недоступен, затем деактивирует сотрудника → виден stale; после восстановления новая заявка на него запрещена, прошлое назначение видно, сессия отозвана. ## Fixture/builders и изоляция Builders исходят из seed.js (6 сотрудников, 3 проекта, demo-1…3, createExamples), добавляют стабильные project/manager/line IDs и явно связывают роли. Один сценарный каталог используется в L2/L4/L5 и стенде; тесты не создают разные копии бизнес-правил. `Clock` фиксируется на дате сценария, IdGenerator детерминирован. После каждого теста БД закрывается и удаляется собственный temporary storage; тесты не делят global state. Сценарии не содержат реальные ФИО/ставки/секреты. Записанные API fixtures хранят version, provenance (какой метод, дата записи, synthetic или sanitized capture) и ожидаемые DTO. Сейчас Time/1С доступны только как **проектируемые synthetic fixtures**: их нельзя считать доказательством совместимости живого API. Перед подключением заменить/дополнить подтверждёнными обезличенными captures и получить контрактный smoke. Опциональный STT-27: `CONTRACT_LIVE=1` и только явно указанный тестовый host; не запускается по умолчанию, тест отклоняет боевой host. Fake-login route включён compile-time только в test build и проверяет NODE_ENV=test также во время исполнения. Отдельный production build проверяется с намеренно переданным fake-флагом: маршрут 404, произвольная test-cookie не аутентифицирует. Публичный демонстрационный стенд — либо test build только с синтетическими данными и явной плашкой, либо обычный build с настоящим тестовым IdP; никакого тестового входа в рабочую сборку. ## Покрытие и команды будущего проекта Порог только `domain/**`: lines/statements/functions ≥90%, branches ≥85%. Основание: ошибки ветвлений решения/полноты опаснее покрытия handlers; 100% по проекту поощряет бессмысленные тесты прокладок. Критические правила REQ-004/005/007/012/017/032/033/038 должны иметь все перечисленные негативные случаи независимо от процентов. Generated DTO/миграции не исключают доменный файл из coverage по удобству; исключения документируются и ревьюятся. `pnpm test:domain`, `test:module`, `test:contract`, `test:api`, `test:components`, `test:e2e`, `test:migrations`, `test:postgres`, `test:prod-safety` — будущие scripts, создаваемые в П1. `pnpm exec tsx scripts/req-coverage` сверяет spec.md с реально собранным vitest/Playwright test inventory и JUnit результатом, а не grep произвольного комментария. Неизвестный REQ, отсутствующий, skipped/todo-only или отсутствующий успешный тест — ненулевой exit. Несколько REQ в имени допустимы при реальной проверке каждого. Новое требование не считается покрытым случайным упоминанием в фикстуре. CI: frozen install → typecheck/lint → L1–L4/компоненты/req-coverage → миграции/Postgres → build/test-production safety → L5. До живых интеграций CI не имеет их credentials. Сначала в пакете пишется падающий тест критерия (RED), затем код (GREEN), затем refactor; MR содержит команды и вывод, а не только проценты. ## Действующая матрица дополнения и проверка прототипа [NEW → код → проверка → документ](addendum-coverage.md) относится к реализации `5213150`, а будущие L1–L5 выше — к рабочему сервису. Наличие тестов прототипа не доказывает готовность интеграций, прав или общей QA. При переносе критериев: Hкал=176,V=16,B=8 → H=152,P(0,5)=76; одинаковые суммы списка/формы/карточки/CSV; unknown против 0; объединение одинаковой даты/праздника/короткого дня; атомарный отказ свыше Hкал; явные часы факта при неизвестной H; ΣFTE·мес=2,25 и 388/512×100; Итог без проектов/после свёртки/по scope; коммерческие и фактические показатели с одним составом людей. Покрытие факта требует всех employee×project×month, отсутствий — employee×month×kind. P и деньги не имеют второго знаменателя продажи. ## Регрессионные критерии принятого поведения Идентификаторы NEW сохранены для связи с историей. Требования включены в spec.md; этот список задаёт повторяемые сценарии проверки. Числа приведены для календаря выше и принятой персональной нормы. - [ ] NEW-01: при 88 ч, R = 4000, C = 4300 → доход 352 000, расход 378 400, разница −26 400 (красная). R = C → 0 (зелёная). Неизвестная C («На подбор») → расход и разница «—». - [ ] NEW-02: колонка «Расход» после «Доход» в форме, рассмотрении и просмотре. Пример (Михаил, «отели», отсутствия 0): окт 176 ч → 756 800 ₽ (красный, доход 704 000), ноя 159 ч → 683 700 ₽ (красный), дек 88 ч → 378 400 ₽ (красный). - [ ] NEW-07: «Расход» между «Доход» и «Статус». Суммы в списке, карточке и сумма месяцев совпадают для всех заявок seed (тест). Для заявки-изменения — «—». - [ ] NEW-03 календарь: `calc` даёт помесячную таблицу 2026–2027 из блока 1 (24 значения в тесте), 1 FTE в ноябре 2026 = 159 ч, 2027-02-20 — рабочий день на 7 ч. - [ ] NEW-03 норма: H = Hкал − отпуск − больничный. Окт: отпуск 16 + больничный 8 → H = 152. Отпуск = Hкал → H = 0 и % «—». Сумма отпуска и больничного > Hкал отклоняется (400). Больничный вводится в «Типы проектов, факт и отпуска» рядом с отпуском, пустое поле удаляет запись, 0 сохраняется. - [ ] FR 62 / AC 61 в новой редакции: удалить отпуск сотрудника → его H, N и проценты по N «—», «норма по календарю» и явно введённые часы факта видны; ввести 0 → значения известны. - [ ] AC 59 в новой редакции: Hкал 176, отпуск 16 → H = N = 160: 0,5 FTE → P = 80 ч и 50 %; явно введённый A = 88 ч → 55 %. Колонок «без отпусков» нет. AC 60: 80 + 132 ч при N 160 + 176 → 63,1 %. - [ ] Словарь, примечание плана и README: `grep -rn "будней × 8\|без праздников\|пока не учитываются" js README.md` → пусто. - [ ] NEW-04: 4 верхние вкладки и 5 подвкладок «Отчётов». В перенесённых подвкладках работают их фильтры, кнопки шапки, раскрытие и режим РП. - [ ] NEW-05: заголовок Σ и значения меняются с единицей (ч / FTE·мес / %). Пример: 0,5 / 1 / 0,75 FTE → Σ 2,25; 388 ч / 512 ч нормы → 75,78125 %, не сумма процентов. - [ ] NEW-06: строка «Итог» = сумма строк сотрудников по месяцам и Σ, проекты не прибавляются повторно, не меняется при свёртке, следует поиску, фильтру свободного ресурса и области РП. - [ ] NEW-08: кольцо коммерческой плановой утилизации рядом с плановым, с теми же фильтрами. Октябрь (все 6 сотрудников, отсутствия заполнены: отпуск Анны 16, прочие 0): N = 1040; коммерческие часы 256 → 24,6 %. - [ ] NEW-09: кольцо фактической утилизации; при неполном факте «—» с пояснением; при полном A / N × 100; при N = 0 «—». В seed — один месяц с полным фактом. - [ ] Дефект вне NEW: «Распределение» и CSV показывают введённые ЦФО и подразделение (отдельный коммит). - [ ] `npm test`, `npm run check` и `git diff --check` проходят. Playwright для РМ и РП на desktop и 320–390 px без JS-ошибок.