Compare commits

...

4 Commits

Author SHA1 Message Date
Дмитрий 364065a239 ci(a11y): provision full PostgreSQL so 14 authenticated Pa11y routes can log in
Pa11y CI был красный: коммит 35387e8b добавил в pa11y.config.json 14
авторизованных маршрутов (dashboard/deals/.../admin/*), которым нужен вход
под admin@demo.local, но a11y.yml поднимал только SQLite без migrate/seed —
а схема Лидерры чисто PostgreSQL (RLS, партиции, роли, raw schema.sql) и на
SQLite не грузится. Логин не проходил → "wait for path /dashboard" таймаут →
красный. Сканировались только 7 публичных страниц.

Теперь a11y-джоб:
- поднимает postgres:16 service-container (liderra/postgres/postgres);
- создаёт 5 ролей БД (db/00_create_roles.sql) — поздние миграции делают
  необёрнутый GRANT ... TO crm_app_user/crm_supplier_worker;
- migrate под postgres-суперюзером (guarded SET ROLE crm_migrator → RESET ROLE);
- partitions:create-months --ahead=2 (demo-сделки за текущий месяц);
- db:seed (APP_ENV=local → DemoSeeder создаёт admin@demo.local + demo-данные);
- .env: Sanctum SPA stateful domains включают localhost:8000 (иначе сессия
  с Pa11y-хоста не залипает), SESSION/CACHE=file, QUEUE=sync, APP_ENV=local.

Покрытие Pa11y: 7 публичных + 14 авторизованных = 21 маршрут.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 14:20:12 +03:00
CoralMinister 000bf816cc Merge pull request #48 from CoralMinister/fix/rossvyaz-osetia
fix(rossvyaz): normalize spaced hyphen to em-dash (Северная Осетия — …
2026-06-03 08:57:01 +03:00
Дмитрий 339c5f09f7 fix(rossvyaz): normalize spaced hyphen to em-dash (Северная Осетия — Алания)
Registry writes 'Республика Северная Осетия - Алания' (hyphen) while the
canonical name uses an em-dash. Replace ' - ' with ' — ' before lookup —
safe because no canonical name contains a space-surrounded hyphen. Unit-tested.
2026-06-03 08:46:32 +03:00
CoralMinister 7a49291296 Merge pull request #47 from CoralMinister/feat/rossvyaz-mapping-tail
feat(rossvyaz): normalize AO / inverted republics / Saha / Kuzbass / …
2026-06-03 08:23:11 +03:00
3 changed files with 96 additions and 6 deletions
+87 -6
View File
@@ -9,6 +9,26 @@ on:
jobs:
a11y:
runs-on: ubuntu-latest
timeout-minutes: 20
# Полноценный PostgreSQL для CI: схема Лидерры — чисто PG (RLS, партиции,
# роли БД, raw schema.sql через load_initial_schema), на SQLite не грузится.
# Без живой БД 14 авторизованных Pa11y-маршрутов не могут залогиниться под
# admin@demo.local → таймаут на "wait for path /dashboard" → красный CI.
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: liderra
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 12
steps:
- name: Checkout
@@ -35,8 +55,27 @@ jobs:
run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader
- name: Install app JS deps
# --legacy-peer-deps: Histoire 1.0-beta.1 заявляет peerDep vite ^7,
# установлено vite 8 (memory feedback_environment.md #74) — как в deploy.yml.
working-directory: app
run: npm ci --no-audit --no-fund
run: npm ci --no-audit --no-fund --legacy-peer-deps
- name: Create PostgreSQL roles
# Базовая schema.sql грузится без ролей (GRANT'ы обёрнуты в DO $$ EXISTS-check),
# но поздние миграции (snapshot, lead-region) делают необёрнутый
# GRANT ... TO crm_app_user/crm_supplier_worker → роли должны существовать.
# SET ROLE crm_migrator в этих миграциях с guard'ом has_schema_privilege →
# под postgres-суперюзером корректно делает RESET ROLE (грантов на public нет).
env:
PGPASSWORD: postgres
run: |
psql -h 127.0.0.1 -U postgres -d liderra -v ON_ERROR_STOP=1 \
-v crm_app_password=ci_pa11y \
-v crm_admin_password=ci_pa11y \
-v crm_migrator_password=ci_pa11y \
-v crm_audit_writer_password=ci_pa11y \
-v crm_supplier_worker_password=ci_pa11y \
-f db/00_create_roles.sql
- name: Bootstrap .env + key
working-directory: app
@@ -44,19 +83,56 @@ jobs:
cp .env.example .env
php artisan key:generate --force
- name: Prepare SQLite for CI (avoid pg-on-CI fixture cost)
- name: Configure .env for CI PostgreSQL + Sanctum SPA
# phpdotenv: первое вхождение ключа выигрывает → не дописываем дубли,
# а удаляем строку и добавляем заново (детерминированный override).
# APP_ENV=local нужен, чтобы DatabaseSeeder вызвал DemoSeeder (admin@demo.local)
# и чтобы session-cookie не был secure-only (вход по http в CI).
# SANCTUM_STATEFUL_DOMAINS обязан включать localhost:8000 — иначе Sanctum
# считает запрос с Pa11y-хоста (localhost:8000) stateless → сессия не залипает.
working-directory: app
run: |
touch database/database.sqlite
sed -i 's/DB_CONNECTION=.*/DB_CONNECTION=sqlite/' .env
sed -i 's|DB_DATABASE=.*|DB_DATABASE=/home/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/app/database/database.sqlite|' .env
setenv() { sed -i "/^$1=/d" .env; echo "$1=$2" >> .env; }
setenv APP_ENV local
setenv APP_DEBUG true
setenv APP_URL http://localhost:8000
setenv DB_CONNECTION pgsql
setenv DB_HOST 127.0.0.1
setenv DB_PORT 5432
setenv DB_DATABASE liderra
setenv DB_USERNAME postgres
setenv DB_PASSWORD postgres
setenv DB_SSLMODE disable
setenv SESSION_DRIVER file
setenv CACHE_STORE file
setenv QUEUE_CONNECTION sync
setenv MAIL_MAILER log
setenv SANCTUM_STATEFUL_DOMAINS localhost:8000,127.0.0.1:8000,localhost,127.0.0.1
- name: Run migrations (postgres superuser → guarded SET ROLE works)
working-directory: app
run: php artisan migrate --force
- name: Create current-month partitions
# schema.sql создаёт baseline-партиции; cron-команда докидывает текущий +2
# месяца (идемпотентно) — нужно для demo-сделок DemoSeeder'а за «сегодня».
working-directory: app
run: php artisan partitions:create-months --ahead=2
- name: Seed demo data (PricingTier + DemoSeeder admin@demo.local)
working-directory: app
run: php artisan db:seed --force
- name: Build frontend assets
working-directory: app
run: npm run build
- name: Start Laravel dev-server
# PHP_CLI_SERVER_WORKERS>1: встроенный сервер обслуживает SPA + sub-resources
# параллельно, чтобы Pa11y-навигации не упирались в однопоточность.
working-directory: app
env:
PHP_CLI_SERVER_WORKERS: 4
run: nohup php artisan serve --host=127.0.0.1 --port=8000 > /tmp/laravel-serve.log 2>&1 &
- name: Wait for dev-server ready
@@ -72,9 +148,14 @@ jobs:
tail -50 /tmp/laravel-serve.log
exit 1
- name: Run Pa11y (live Vue)
- name: Run Pa11y (live Vue, 7 public + 14 authenticated routes)
run: npm run a11y
- name: Laravel log tail on failure
if: failure()
working-directory: app
run: tail -120 storage/logs/laravel.log || echo "no laravel.log"
- name: Upload Pa11y screenshots
if: always()
uses: actions/upload-artifact@v4
+4
View File
@@ -169,6 +169,10 @@ final class RussianRegions
// «обл.» → «область»; « АО» → « автономный округ».
$name = (string) preg_replace('/\s*обл\.$/u', ' область', $segment);
$name = (string) preg_replace('/\s+АО$/u', ' автономный округ', $name);
// Дефис с пробелами → длинное тире (эталон: «Республика Северная Осетия — Алания»).
// Безопасно: ни одно каноническое имя не содержит дефис, окружённый пробелами
// (составные имена вроде «Кабардино-Балкарская» используют дефис без пробелов).
$name = str_replace(' - ', ' — ', $name);
if (isset(self::nameToCode()[$name])) {
return $name;
@@ -95,3 +95,8 @@ it('maps Moscow / SPb spelling variants', function (): void {
expect(RussianRegions::resolveSubjectCode('Город Москва'))->toBe(82)
->and(RussianRegions::resolveSubjectCode('г. Санкт - Петербург'))->toBe(83);
});
it('normalizes spaced hyphen to em-dash (Северная Осетия — Алания)', function (): void {
expect(RussianRegions::resolveSubjectCode('Республика Северная Осетия - Алания'))->toBe(18)
->and(RussianRegions::resolveSubjectCode('г. Владикавказ|Республика Северная Осетия - Алания'))->toBe(18);
});