# Проверки и требования к тестируемости Это план будущих тестов сервиса, не отчёт об их выполнении. В 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 на календаре prototype-weekdays. Дополнительно утверждённый производственный календарь с рабочим выходным/7-часовым днём. 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 содержит команды и вывод, а не только проценты.