From 1109f68e11bdedb54637fd8617be841df7d9027c Mon Sep 17 00:00:00 2001 From: kamask Date: Mon, 26 Oct 2026 19:46:45 +0300 Subject: [PATCH] test(api): type-check harness and cover automigrate and auth isolation - add tsconfig.tests.json and run tsc in test:api - move vitest ProvidedContext types to provided-context.d.ts - rename vitest.api.config.ts to .mts - kill signal-test fixture with SIGTERM first so cleanup still runs - add tests: no files in pb_migrations, independent user/guest clients - docs: drop superuser env vars from tests in README, note tsc in testing.md Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01EvmtStx737hu8gXe15e55W --- README.md | 2 +- docs/testing.md | 2 + package.json | 2 +- tests/api/global-setup.ts | 1 - tests/api/harness.api.spec.ts | 40 ++++++++++++++++--- tests/support/clients.ts | 8 ---- tests/support/provided-context.d.ts | 10 +++++ tsconfig.json | 3 ++ tsconfig.tests.json | 12 ++++++ vitest.api.config.ts => vitest.api.config.mts | 0 10 files changed, 63 insertions(+), 17 deletions(-) create mode 100644 tests/support/provided-context.d.ts create mode 100644 tsconfig.tests.json rename vitest.api.config.ts => vitest.api.config.mts (100%) diff --git a/README.md b/README.md index 73629ee..c08ea37 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,6 @@ PB_URL=http://127.0.0.1:8090 PORT=4000 node dist/pchelpcrm/server/server.mjs | `PB_URL` | внутренний адрес PocketBase для SSR-сервера и dev-прокси, по умолчанию `http://127.0.0.1:8090` | | `PORT` | порт Node SSR-сервера, по умолчанию `4000` | | `PB_DIR` | папка данных PocketBase для скрипта `pb:superuser` | -| `PB_SUPERUSER_EMAIL`, `PB_SUPERUSER_PASSWORD` | суперпользователь PocketBase (скрипт `pb:superuser` и тесты) | +| `PB_SUPERUSER_EMAIL`, `PB_SUPERUSER_PASSWORD` | суперпользователь PocketBase (скрипт `pb:superuser`) | Резервная копия — копирование папки `pb_data`. После первого релиза приложение планируется поставлять одним Docker-контейнером (SSR-сервер и PocketBase вместе). diff --git a/docs/testing.md b/docs/testing.md index 9cb98bd..8149ee6 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -14,6 +14,8 @@ Команды появляются по мере того, как настраиваются соответствующие стенды. +`pnpm test:api` сначала проверяет типы тестового кода (`tsconfig.tests.json`: `tests/**` и `vitest.api.config.mts`), затем запускает Vitest. + ## Контрактные тесты репозиториев У каждого репозитория две реализации: PocketBase (`data/`) и фейк в памяти (`testing/`). Чтобы фейк не расходился с реальностью, поведение описывается **один раз**: diff --git a/package.json b/package.json index b651106..69b7557 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test", - "test:api": "vitest run --config vitest.api.config.ts", + "test:api": "tsc -p tsconfig.tests.json && vitest run --config vitest.api.config.mts", "lint": "eslint .", "lint:fix": "eslint . --fix", "serve:ssr:pchelpcrm": "node dist/pchelpcrm/server/server.mjs", diff --git a/tests/api/global-setup.ts b/tests/api/global-setup.ts index 480523a..2de6170 100644 --- a/tests/api/global-setup.ts +++ b/tests/api/global-setup.ts @@ -1,6 +1,5 @@ import type { TestProject } from 'vitest/node'; import { startPocketBase } from '../support/pocketbase-server'; -import '../support/clients'; export default async function setup(project: TestProject) { const { url, dataDir, superuser, stop } = await startPocketBase(); diff --git a/tests/api/harness.api.spec.ts b/tests/api/harness.api.spec.ts index 879f136..17d0faa 100644 --- a/tests/api/harness.api.spec.ts +++ b/tests/api/harness.api.spec.ts @@ -1,6 +1,7 @@ import { spawn } from 'node:child_process'; +import { randomUUID } from 'node:crypto'; import { once } from 'node:events'; -import { existsSync } from 'node:fs'; +import { existsSync, readdirSync } from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { describe, expect, inject, it, vi } from 'vitest'; @@ -9,6 +10,7 @@ import { type PocketBaseServer, startPocketBase } from '../support/pocketbase-se const health = (url: string) => fetch(`${url}/api/health`); const holdFixture = path.resolve('tests/support/fixtures/hold-pocketbase.ts'); +const migrationsDir = path.resolve('pocketbase/pb_migrations'); describe('стенд API-тестов', () => { it('отвечает 200 на GET /api/health', async () => { @@ -60,13 +62,32 @@ describe('стенд API-тестов', () => { expect(pb.authStore.record?.['email']).toBe(user.email); }); - it('у каждого хелпера свой экземпляр SDK', async () => { - const superuser = await asSuperuser(); + it('клиенты двух пользователей и гостя не делят авторизацию', async () => { + const a = await createUser(); + const b = await createUser(); + + const pbA = await asUser(a); + const pbB = await asUser(b); const guest = asGuest(); - expect(guest).not.toBe(superuser); - expect(guest).not.toBe(asGuest()); + expect(pbA.authStore.record?.id).toBe(a.id); + expect(pbB.authStore.record?.id).toBe(b.id); expect(guest.authStore.isValid).toBe(false); + expect((await pbA.collection('users').getOne(a.id)).id).toBe(a.id); + expect((await pbB.collection('users').getOne(b.id)).id).toBe(b.id); + }); + + it('изменения коллекций не порождают файлы в pocketbase/pb_migrations', async () => { + const before = readdirSync(migrationsDir); + + await ( + await asSuperuser() + ).collections.create({ + name: `automigrate_probe_${randomUUID().replaceAll('-', '')}`, + type: 'base', + }); + + expect(readdirSync(migrationsDir)).toEqual(before); }); it('работает на отдельном временном PocketBase, а не на pocketbase/pb_data', async () => { @@ -134,7 +155,14 @@ describe('startPocketBase', { timeout: 30_000 }, () => { await expect(health(url)).rejects.toThrow(); expect(existsSync(dataDir)).toBe(false); } finally { - child.kill('SIGKILL'); + // Если тест упал до сигнала, даём владельцу убрать PocketBase сам; SIGKILL — только по таймауту. + if (child.exitCode === null && child.signalCode === null) { + const exited = once(child, 'exit'); + child.kill('SIGTERM'); + const timer = setTimeout(() => child.kill('SIGKILL'), 5_000); + await exited; + clearTimeout(timer); + } } }, ); diff --git a/tests/support/clients.ts b/tests/support/clients.ts index 69be96c..ae8308c 100644 --- a/tests/support/clients.ts +++ b/tests/support/clients.ts @@ -3,14 +3,6 @@ import { randomUUID } from 'node:crypto'; import PocketBase, { BaseAuthStore } from 'pocketbase'; import { inject } from 'vitest'; -declare module 'vitest' { - export interface ProvidedContext { - pbUrl: string; - pbDataDir: string; - pbSuperuser: { email: string; password: string }; - } -} - export interface TestUser { id: string; email: string; diff --git a/tests/support/provided-context.d.ts b/tests/support/provided-context.d.ts new file mode 100644 index 0000000..93d6b38 --- /dev/null +++ b/tests/support/provided-context.d.ts @@ -0,0 +1,10 @@ +// Типы значений, которые global-setup отдаёт тестам через `project.provide` / `inject`. +import 'vitest'; + +declare module 'vitest' { + export interface ProvidedContext { + pbUrl: string; + pbDataDir: string; + pbSuperuser: { email: string; password: string }; + } +} diff --git a/tsconfig.json b/tsconfig.json index 22182b3..767672b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,9 @@ }, { "path": "./tsconfig.spec.json" + }, + { + "path": "./tsconfig.tests.json" } ] } diff --git a/tsconfig.tests.json b/tsconfig.tests.json new file mode 100644 index 0000000..293b7e9 --- /dev/null +++ b/tsconfig.tests.json @@ -0,0 +1,12 @@ +/* Проверка типов стенда API-тестов и вспомогательного кода: запускается из `pnpm test:api`. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "strict": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "types": ["node"] + }, + "files": [], + "include": ["tests/**/*.ts", "vitest.api.config.mts"] +} diff --git a/vitest.api.config.ts b/vitest.api.config.mts similarity index 100% rename from vitest.api.config.ts rename to vitest.api.config.mts