Sprint 2 Phase A (modernization). Закрытие audit O-stack-01/02/03 + O-perf-07:
- O-stack-01: Pest 4 browser-tests scaffold. testsuite Browser в phpunit.xml,
Pest.php extend(...)->in('Browser'), tests/Browser/SmokeTest.php
(login-flow + deal-create-flow). На Windows native PHP плагин
pest-plugin-browser требует ext-sockets (отсутствует в стандартной
сборке) и при автозагрузке вызывает socket_create_listen() ДО старта
тестов, что ломает весь Pest. Поэтому плагин НЕ в require-dev,
тесты помечены ->skip(...) с инструкцией активации на Linux CI.
Скелет тестов в комментариях — на CI достаточно
composer require pestphp/pest-plugin-browser --dev + npx playwright
install + раскомментировать тела.
- O-stack-02: infection/infection ^0.32.7 в require-dev + app/infection.json
(minMsi=50, source: Http/Models/Services). composer mutation script.
Запуск отдельной задачей в CI (медленный). allow-plugins для
infection/extension-installer.
- O-stack-03: Laravel 13 string-based lazy-loading в routes/web.php —
убран блок use App\Http\Controllers\Api\* (16 импортов), все
ссылки заменены на строки 'App\Http\...\X@method'. Контроллеры
не загружаются автозагрузчиком при boot route'ов; класс резолвится
только при матче маршрута. Использован строковый синтаксис, а не
FQN-class — Pint default preset (fully_qualified_strict_types fixer)
сворачивает FQN-class обратно в use, на строки не действует.
- O-perf-07: Larastan result cache через tmpDir: .phpstan-cache в
phpstan.neon (cache-dir не дублируется флагом — phpstan не принимает
оба источника). lefthook job 6 (larastan) использует этот же
tmpDir автоматически. Ускорение инкрементальных pre-commit прогонов:
на правке одного файла — переанализ только зависящих, не всего
графа классов. .phpstan-cache в .gitignore.
Pest: 416/416 PASS + 2 skipped (browser smoke pending Linux CI).
Larastan: 0 errors above baseline.
Infection: vendor/bin/infection --version → 0.32.7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Реализованы кейсы 1 + 4 из Прил. И Г.1 «CTO-13: RLS smoke-test через
PgBouncer» как первая проверка RLS-фундамента schema v8.6 ДО первого
PR с tenant-моделью.
app/tests/Feature/RlsSmokeTest.php (NEW):
- кейс 1 (× 2 теста): SET LOCAL app.current_tenant_id изолирует SELECT
в deals — оба тенанта видят только свои 2 deals из 4 общих.
- кейс 1 расширенный: RLS работает на projects (не только deals) —
тот же tenant-контекст применяется ко всем 36 политикам.
- кейс 4: WITH CHECK блокирует INSERT в projects с чужим tenant_id —
ожидается QueryException (RLS WITH CHECK violation).
Стек теста:
- testing-роль `testing_rls_user` NOLOGIN (создаётся идемпотентно через
DO $$ ... IF NOT EXISTS $$). На dev superuser обходит RLS — поэтому
через SET LOCAL ROLE переключаемся на NOLOGIN-роль без BYPASSRLS.
- DatabaseTransactions trait вместо RefreshDatabase — каждый тест в
транзакции, ROLLBACK сбрасывает SET LOCAL ROLE и тестовые данные.
- Отдельная БД liderra_testing (создана `CREATE DATABASE` через psql,
мигрирована `DB_DATABASE=liderra_testing artisan migrate:fresh` 743 ms).
- phpunit.xml: DB_CONNECTION sqlite → pgsql, DB_DATABASE liderra_testing.
Pest 6/6 passed (RlsSmokeTest 4/4 + ExampleTest 2/2) за 723 ms total.
Кейсы НЕ покрытые (отложены):
- Кейс 2-3 (PgBouncer transaction-pooling reuse, job retry): production-
только, на native Windows-стеке нет PgBouncer
- Кейс 5 (REVOKE на 6 saas-таблицах для crm_app_user): требует ролей
из db/02_grants.sql, на dev не созданы (только postgres-superuser)
Сопутствующие правки:
- .gitleaks.toml: + allowlist path для app/tests/*.php (фиктивные
телефоны вида +79000010001 в фикстурах — не реальные ПДн)
- app/phpstan-baseline.neon: regenerated — Pest dynamic $this properties
($this->tenant1Id и т.п.) не парсятся PHPStan без pest-extension,
занесены в baseline (12 entries) до миграции на typed-properties
- CLAUDE.md §6: Pest 2/2 → 6/6, добавлено упоминание CTO-13 smoke-test
- memory project_state.md, MEMORY.md: smoke-test реализован
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>