Тестування
У монорепозиторії кожен пакет тестується окремо: backend і frontend — через Vitest, shared і documentation — через вбудований node:test. Кореневий скрипт pnpm test запускає pnpm -r test для всіх пакетів (package.json). Нижче основна увага — backend.
Backend: конфігурація Vitest
Конфіг packages/backend/vitest.config.ts мінімальний:
environment: "node";exclude: стандартніconfigDefaults.excludeплюсdist/**,**/*.redis.test.tsі**/*.pg.test.ts(їх запускають окреміtest:redisіtest:pg, див. розділ «Інтеграційні тести з Redis і PostgreSQL» нижче). Окремогоincludeнемає, тож Vitest підхоплює стандартні шаблони*.spec.ts/*.test.ts;testTimeout: 15_000— 15 с на тест замість стандартних 5 с: під паралельним навантаженням (typecheck + lint) 5 с не вистачало;maxWorkersбереться зVITEST_MAX_WORKERS, якщо змінну задано (у CI —2); без неї діє значення Vitest за замовчуванням;- аліаси
#app,#config,#vendor,#database,#drizzleвказують на відповідні каталогиsrc/, а#logger— наsrc/vendor/utils/logger.ts.
setupFiles і секції coverage у конфігу немає.
Скрипти пакета (packages/backend/package.json):
| Скрипт | Команда |
|---|---|
test | vitest run |
test:watch | vitest |
Тести лежать поруч із кодом як *.spec.ts (наприклад, csrf-guard.ts і csrf-guard.spec.ts). Реальний прогін pnpm --filter backend test на гілці документації дав: 51 файл, 268 тестів, усі пройшли, ~2,2 с. Vitest запускає кожен файл в окремому воркері (ізоляція за замовчуванням).
Категорії тестів
| Категорія | Приклади |
|---|---|
| Старт сервера (vendor) | server.spec.ts (handleError пише 422 до заголовків), router.spec.ts, static-server.spec.ts, http-validation.spec.ts |
| Маршрутизація / WS | serialize-routes.spec.ts, ws-validation.spec.ts (422 і невиклик handler при невалідному payload), define-route.spec.ts |
| Сервіси | auth-service.spec.ts, knowledge-base-service.spec.ts, push-subscription-service.spec.ts |
| Middleware | csrf-guard.spec.ts (мокає #config/csrf.js і будує фейковий HttpContext), session-web.spec.ts |
| Валідація / адаптери | arktype-adapter.spec.ts, ws-message-validator.spec.ts |
| Утиліти | rate limit (rate-limit-store.spec.ts — обмежений стан, точний Retry-After, межа ключів; http-rate-limit.spec.ts, upgrade-rate-limit.spec.ts, server-rate-limit.spec.ts — охоплення L1; rate-limit.transport.spec.ts — справжній uWS: тіло POST не губиться, handshake 101/429), сесії/CSRF-токени, s3.spec.ts, logger.spec.ts |
Патерни мокання
- Barrel-модулі. Сервіси мокають
#app/repositories/index.jsі#app/transformers/index.js, а потім динамічно імпортують сервіс. Детально — у «Сервіси», «Репозиторії» і «Трансформери»; повний приклад — крок 9 у «Додавання фічі». - Окремі модулі. Деякі тести мокають конкретний файл, наприклад
knowledge-base-service.spec.tsмокає#app/repositories/support/support-knowledge-repository.js,support-embedding-service.jsі#logger. - Конфіг.
vi.mock("#config/csrf.js", ...)уcsrf-guard.spec.tsпідставляє конфіг зenforce: true. - uWebSockets.js.
server.spec.tsіhttp-validation.spec.tsмокаютьuWebSockets.js(App: () => ({})), маршрути,ws-handlersта HTTP-middleware, аHttpResponseпідміняють об'єктом ізvi.fn()дляcork/writeStatus/writeHeader/end. Справжній сервер не стартує, портів ніхто не слухає (у spec-файлах немає викликівlisten(). - Часткові моки.
vi.importActualзберігає решту модуля, підмінюючи одну функцію (getDataуhttp-validation.spec.ts).
Жоден spec-файл не мокає #database/db.js і не згадує Redis, тож для pnpm --filter backend test не потрібні ні БД, ні Redis.
Інтеграційні тести з Redis і PostgreSQL
| Суфікс | Конфіг | Скрипт | Змінна | CI |
|---|---|---|---|---|
*.redis.test.ts | vitest.redis.config.ts | pnpm --filter backend test:redis | REDIS_TEST_URL | redis-tests.yml |
*.pg.test.ts | vitest.pg.config.ts | pnpm --filter backend test:pg | PG_TEST_URL | pg-tests.yml |
Без змінної всі suites пропускаються (describe.skipIf), тож скрипти безпечно запускати будь-де. Файли виконуються послідовно (fileParallelism: false).
PostgreSQL. Перед тестами pg-global-setup.ts через PG_TEST_URL (адмінське з'єднання, зазвичай до бази postgres) створює тимчасову базу vibe_test_<random>, вмикає vector, застосовує міграції з src/drizzle/migrations у ту саму таблицю drizzle.__drizzle_migrations, що й db:migrate, і виставляє PGSQL_* на цю базу. Тому db з #database/db.js і всі репозиторії в тестах працюють зі справжньою схемою без моків. Після прогону база видаляється (DROP DATABASE … WITH (FORCE)). Потрібен образ з pgvector — той самий pgvector/pgvector:pg16, що в CI.
Локальний запуск:
docker run -d --rm --name vibe-pg-test -e POSTGRES_PASSWORD=postgres \
-p 127.0.0.1:55433:5432 pgvector/pgvector:pg16
PG_TEST_URL=postgres://postgres:postgres@127.0.0.1:55433/postgres pnpm --filter backend test:pg
docker stop vibe-pg-testНовий pg-тест: файл <name>.pg.test.ts поруч із кодом, describe.skipIf(!hasPg) з pg-test-db.ts, #database/db.js імпортується динамічно всередині describe, а в afterAll — pool.end(). Тестові дані кожен файл створює й прибирає сам: база спільна для всього прогону. Зразок — harness.pg.test.ts.
Coverage
Backend і frontend мають скрипт test:coverage (vitest run --coverage з провайдером @vitest/coverage-v8, звіти text-summary у консоль і html у coverage/):
pnpm --filter backend test:coverage
pnpm --filter frontend test:coverageПорогу немає: CI лише публікує звіт. Coverage рахується по файлах, які завантажили тести, а не по всьому src/. Стартові значення (рядки, жовтень 2026): backend ~74 %, frontend ~51 %.
Інші пакети
- shared —
"test": "pnpm build && node --test test/*.test.js"(package.json): спочатку збірка, потімnode:testнад test/languages.test.js. - frontend — Vitest у
jsdomповерхvite.config, з виключеннямe2e/**,e2e-csp/**іe2e-security/**(vitest.config.ts); unit-тести вsrc/**/__tests__. Скрипти:test(vitest run),test:unit(vitest),test:e2e(playwright test). Playwright (playwright.config.ts) бере тести зe2e/, проєкти chromium, firefox, webkit і Mobile Safari, аwebServerсам піднімаєpnpm run devна 5173 (на CI —pnpm run previewна 4173) або перевикористовує запущений. Backend Playwright не запускає. П'ятий проєктmock(тести вe2e/mock/, лише chromium) перевіряє стани модалок входу, реєстрації та відновлення пароля на підроблених відповідях API: кожен запит до/apiобслуговує сам тест черезpage.route(e2e/mock/support.ts), а запит без відповіді валить тест. Для нього конфіг збирає окрему копію SPA вdist-e2e-mock/зVITE_AUTH_PHONE_ENABLED=true,VITE_BASE_URLна власний origin і вимкненим Sentry та віддає її черезvite previewна порту 4174, тож результат не залежить від.envрозробника. Тести фіксують поточну поведінкуAuthModals.vueяк є, зокрема й дивну (успішна відповідь без користувача показується як помилка; Enter у полі логіну нічого не робить); їхні очікування змінюються тільки свідомо, разом із рефакторингом цього файлу. Окремий конфіг playwright.live.config.ts з тестами вe2e-live/(test:e2e:live) проганяє SPA через справжній бекенд із PostgreSQL (pgvector), Redis і поштовим приймачем:e2e-live/mail-sink.ts— мінімальний SMTP-сервер на 2526, у який бекенд шле листи (SMTP_IGNORE_TLS=true), тож тест читає посилання з листа так, як його натиснув би користувач. Скрипт збирає SPA без phone auth з originhttp://localhost:3100, аwebServerспершу створює базуvibe_e2e_live(db:create,db:migrate,db:seed --demo) і лише потім запускає бекенд зCSRF_ENFORCE=true; префікс ключів Redis новий на кожен запуск, тож лічильники throttle попереднього не заважають. Кожен браузер сценарію — окремийBrowserContextзі своєю адресою вX-Forwarded-For(TRUST_PROXY=trueлише для localhost): інакше усі вони — 127.0.0.1, і ліміт 10 запитів на хвилину на групуauthзупиняє прогін. Сценарії: скидання пароля листом, зміна пароля на сторінці акаунта та «вийти всюди» закривають сесії інших браузерів (API відповідає401, сторінка показує вхід; відкритий WebSocket відкликаної сесії сервер закриває лише на наступному запиті клієнта, див. WebSocket), старий пароль не діє, нова працює, посилання скидання одноразове; реєстрація → лист → підтвердження → вхід, де ранній вхід дає те саме401, а повторний лист робить перше посилання недійсним; лічильник реєстрації на адресу справжній (четверта спроба за годину —400). Потрібні PostgreSQL і Redis; у CI їх дає jobe2e-live. CSP перевіряє окремий конфіг playwright.csp.config.ts з тестами вe2e-csp/:test:e2e:cspзбирає production-статику (scripts/prod-local.sh --build-only) і двічі запускає бекенд зSERVE_STATIC=true— у режимі enforce і report-only. Перевіряються дозволені сценарії: реєстрація, WebSocket (до кінця handshake — очікується фреймpongу відповідь на heartbeatping), шрифти, service worker offline і зображення зCDN_URL(naturalWidth > 0; роль CDN грає заглушкаe2e-csp/cdn-stub.mjs). Заборонені: inline-скрипт (effectiveDirectivescript-src-elem),fetchпозаconnect-srcі зображення позаimg-srcна локальний probe-origin, з перевіркоюdisposition(enforce/report). Потрібні PostgreSQL і Redis. CSRF і service worker перевіряє playwright.security.config.ts з тестами вe2e-security/:test:e2e:securityзбирає production-статику і запускає бекенд зCSRF_ENFORCE=true,CSRF_STRICT_ORIGIN=trueіCSRF_ALLOWED_ORIGINS;global-setup.tsстворює два демо-акаунти черезdb:seed --demo. Сценарії: холодний вхід (init→401→ login з токеном), logout → login, застарілий токен при живій сесії (рівно один повтор), втрачена сесія і зміна акаунта в іншій вкладці (без повтору, у другому випадку — перезавантаження під новим користувачем), відхилені мутації без побічного ефекту (чужийOrigin,Sec-Fetch-Site: cross-site, без токена, безOrigin), і відсутність API-відповідей у Cache Storage. «Інша вкладка» —page.request, що ділить cookie з контекстом браузера. Потрібні PostgreSQL і Redis.
Не покрито
Бекенд запускається з APP_ENV=local по http, тому production-гілка (upgrade-insecure-requests, HTTPS/WSS) і реальні S3/CDN (завантаження аватара, редирект /support/screenshot/:id) у браузерних тестах не перевіряються.
- documentation —
node --test "scripts/verify/*.test.mjs"(package.json) перевіряє хелпери скрипта verify-dist (lib.test.mjs).
CI і локальний запуск
У .github/workflows три workflow:
| Workflow | Коли | Що робить |
|---|---|---|
| ci.yml | кожен PR і push у main, без фільтра paths | job quality: pnpm install --frozen-lockfile → pnpm --filter shared build → format:check → typecheck → lint → test → test:coverage (artifact coverage); job e2e: shared build → pnpm --filter frontend build → playwright install --with-deps (усі браузери; кеш ~/.cache/ms-playwright за версією Playwright) → test:e2e на 5 проєктах (artifact playwright-report при падінні); job e2e-live (сервіси PostgreSQL з pgvector і Redis): pnpm --filter frontend test:e2e:live на chromium (artifact playwright-live-report при падінні) |
| pg-tests.yml | PR і push зі змінами backend/shared | pnpm --filter backend test:pg із сервісом pgvector/pgvector:pg16; єдине місце, де запускаються *.pg.test.ts |
| redis-tests.yml | PR і push зі змінами backend/shared | pnpm --filter backend test:redis із сервісом redis:7-alpine; єдине місце, де запускаються *.redis.test.ts |
| docs.yml | зміни документації | typecheck, test, docs:build, docs:verify для пакета documentation і деплой на GitHub Pages з main |
shared збирається перед перевірками, бо typecheck і тести backend/frontend імпортують shared/dist. Playwright у CI запускає pnpm run preview по зібраному dist, тож frontend build іде перед e2e.
Локально те саме, крім e2e і coverage, проганяє init.sh (install, typecheck, lint, pnpm test, build).
Рецепти
Тест сервісу. Створіть <name>-service.spec.ts поруч із сервісом, замокайте barrel репозиторіїв і перевірте Result. Готовий приклад — «Додавання фічі», крок 9.
Тест маршруту / валідації. Візьміть за зразок http-validation.spec.ts: замокайте uWebSockets.js, #app/routes/http-routes.js, #app/routes/ws-routes.js, ws-handlers і execute-httpMiddlewares, оголосіть маршрут через defineRoute зі схемою arktype і перевірте, що на невалідному тілі відповідь 422, а handler не викликається. Для WS — аналогічно ws-validation.spec.ts. Про схеми див. «Валідація».
Запуск:
pnpm --filter backend test # усі тести один раз
pnpm --filter backend test:watch # watch-режим
pnpm --filter backend test csrf-guard # фільтр за шляхом файлу (аргумент vitest)
pnpm --filter backend exec vitest run -t "422" # фільтр за назвою тесту (через `test -t` pnpm не передає прапорець)
pnpm --filter frontend test:e2e # Playwright
pnpm --filter frontend test:e2e:csp # CSP на production-збірці через бекенд
pnpm --filter frontend test:e2e:security # CSRF і service worker через бекенд з enforceПрогалини
- Тестів репозиторіїв поки немає: інфраструктура
*.pg.test.tsє, але, крім smoke-тесту, шар даних у тестах мокається. - Coverage без порогу і лише по завантажених тестами файлах.