fix: make SQLite migrations and e2e reproducible
This commit is contained in:
@@ -36,18 +36,15 @@ Unit specs colocated в `src/**/*.spec.ts`; HTTP/integration — `test/`.
|
|||||||
Полный `npm run verify` включает lint, оба typecheck, Jest и build.
|
Полный `npm run verify` включает lint, оба typecheck, Jest и build.
|
||||||
`npm run test:openapi-contract` — отдельный required check при wire contract change.
|
`npm run test:openapi-contract` — отдельный required check при wire contract change.
|
||||||
|
|
||||||
`npm run test:e2e` существует, но известный Prisma migration blocker описан в
|
`npm run test:e2e` запускает все HTTP suites через изолированный SQLite runner;
|
||||||
[ограничении сквозной проверки](../../../docs/development/verification.md#известное-ограничение-сквозной-проверки) и WL-P2-05
|
`npm run test:sqlite` — только auth/cards. Каждая suite получает новую временную
|
||||||
[registry](../../../docs/development/audit-remediation.md). Скрипт использует
|
базу со штатными migrations. Прямой Jest без runner отклоняется до DB-запросов.
|
||||||
фиксированную `/tmp/wealth-ledger-e2e.db`; не запускай его конкурентно как
|
`prisma.config.ts` исключает влияние унаследованного `RUST_LOG=warn/error`,
|
||||||
независимые DB tests. Для isolation выбери отдельную test DB и runner configuration.
|
вызывающего ошибку fresh migration на Prisma 7.9.1. Подробности и регрессия запуска —
|
||||||
Если задаче нужна HTTP/DB proof, установи применимость blocker и выполни доступную
|
|
||||||
изолированную проверку. Не объявляй покрытым то, что до tests не запустилось;
|
|
||||||
сохрани точный failure и оставшуюся проверку. Unit/Swagger success не заменяет e2e.
|
|
||||||
|
|
||||||
Для auth/cards DB-инвариантов используйте `npm run test:sqlite`: отдельная
|
|
||||||
временная база на suite, migrations с `RUST_LOG=info`, configured HTTP и реальная
|
|
||||||
Prisma. Для schema/data changes дополнительно `npm run test:durability -- <baseline>`.
|
|
||||||
Матрица рисков и подготовка совместимой предыдущей сборки —
|
|
||||||
[Backend README](../../README.md#реальная-sqlite-и-сохранность-данных).
|
[Backend README](../../README.md#реальная-sqlite-и-сохранность-данных).
|
||||||
Эти проверки не снимают ограничение общего `test:e2e` автоматически.
|
HTTP suites требуют разрешения открывать локальные порты. Ошибка sandbox
|
||||||
|
не считается успешным тестом; повторите проверку в подходящем окружении.
|
||||||
|
|
||||||
|
Для schema/data changes дополнительно `npm run test:durability -- <baseline>`.
|
||||||
|
Матрица рисков и подготовка совместимой предыдущей сборки — в том же README.
|
||||||
|
Mock Prisma не заменяет доказательство реальной DB concurrency.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ zero-warning ESLint, application/test TypeScript, Jest и build.
|
|||||||
|
|
||||||
HTTP contract changes дополнительно проходят общий
|
HTTP contract changes дополнительно проходят общий
|
||||||
[contract workflow](../docs/development/runtime-openapi-verification.md).
|
[contract workflow](../docs/development/runtime-openapi-verification.md).
|
||||||
Общий e2e имеет известный environment blocker; применимость и маршрут проверки
|
Общий e2e запускается на отдельных временных DB; применимость и маршрут проверки
|
||||||
описаны в [backend testing](.ai/conventions/backend-testing.md).
|
описаны в [backend testing](.ai/conventions/backend-testing.md).
|
||||||
Generated Prisma client не редактируется вручную. Credentials, авторизация
|
Generated Prisma client не редактируется вручную. Credentials, авторизация
|
||||||
доступа к счетам и внешние интеграции остаются server-side.
|
доступа к счетам и внешние интеграции остаются server-side.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ NestJS API WealthLedger. Backend владеет auth, SQLite-данными и s
|
|||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
nvm use --silent
|
nvm use --silent
|
||||||
npm ci
|
npm ci
|
||||||
|
npx prisma migrate deploy
|
||||||
npm run start:dev
|
npm run start:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -31,9 +32,10 @@ npm run verify
|
|||||||
Он запускает zero-warning lint,
|
Он запускает zero-warning lint,
|
||||||
application/test typechecks, Jest и build. `npm run lint:fix` — отдельная
|
application/test typechecks, Jest и build. `npm run lint:fix` — отдельная
|
||||||
записывающая команда, `npm run test:cov` — диагностика без общего числового
|
записывающая команда, `npm run test:cov` — диагностика без общего числового
|
||||||
threshold. `npm run test:e2e` пока не является passing gate: Prisma schema
|
threshold. `npm run test:e2e` отдельно проверяет все HTTP suites на новых базах
|
||||||
engine останавливает migration до запуска Jest. Точный статус и trigger
|
и runtime OpenAPI с соседним frontend checkout. Для полной backend-проверки
|
||||||
пересмотра находятся в [remediation registry](../docs/development/audit-remediation.md).
|
выполните обе команды. Результаты — в
|
||||||
|
[remediation registry](../docs/development/audit-remediation.md).
|
||||||
|
|
||||||
Общий `tsconfig.json` включает `strict: true` и `noFallthroughCasesInSwitch`
|
Общий `tsconfig.json` включает `strict: true` и `noFallthroughCasesInSwitch`
|
||||||
для приложения, тестов и сборок. ESLint запрещает non-null assertions
|
для приложения, тестов и сборок. ESLint запрещает non-null assertions
|
||||||
@@ -72,12 +74,26 @@ npm run test:openapi-contract
|
|||||||
## Реальная SQLite и сохранность данных
|
## Реальная SQLite и сохранность данных
|
||||||
|
|
||||||
После `nvm use --silent` запускайте `npm run test:sqlite` при изменении auth,
|
После `nvm use --silent` запускайте `npm run test:sqlite` при изменении auth,
|
||||||
карточных операций или DB boundary. Runner запускает существующие HTTP suites
|
карточных операций или DB boundary, `npm run test:e2e` — для всех HTTP suites.
|
||||||
последовательно, каждую на новой `/tmp/bank-cards-*/test.db`, применяет migrations
|
Обе команды используют один runner: отдельный каталог `wealthledger-e2e-*`
|
||||||
с `RUST_LOG=info` и удаляет только созданную им директорию. Внешний `DATABASE_URL`
|
в системном temp на каждую suite, один `DATABASE_URL` для migrations и Jest.
|
||||||
заменяется временным; `.env` и пользовательские данные не изменяются. Ошибка
|
Внешний `DATABASE_URL` заменяется временным; `.env` и пользовательские данные
|
||||||
migration или Jest возвращает ненулевой exit code. Нужен доступ к loopback-порту.
|
не изменяются. Прямой Jest без runner отклоняется до обращения к базе.
|
||||||
Это отдельный scope, а не подтверждение всего `test:e2e` (задача 03).
|
При ошибке migration/Jest или SIGINT/SIGTERM runner останавливает дочерние
|
||||||
|
процессы, удаляет свой каталог и возвращает ненулевой exit code.
|
||||||
|
Нужен доступ к локальным HTTP-портам; sandbox может его запрещать.
|
||||||
|
|
||||||
|
`prisma.config.ts` удаляет унаследованный `RUST_LOG` только в процессе Prisma CLI:
|
||||||
|
на Prisma 7.9.1 уровни `warn`/`error` вызывают пустой `Schema engine error`
|
||||||
|
при fresh migration, без переменной миграция проходит. Постоянный
|
||||||
|
`RUST_LOG=info` и обновление зависимостей не нужны. Logging приложения
|
||||||
|
настраивается отдельно. История migrations применяется штатным `migrate deploy`.
|
||||||
|
|
||||||
|
Регрессия самого запуска: `node test/sqlite-regression.cjs`. Она выполняет
|
||||||
|
три последовательных и два параллельных полных e2e, проверяет уникальность DB,
|
||||||
|
сохранность вымышленного внешнего файла, отказ migration и SIGINT/SIGTERM cleanup.
|
||||||
|
Логи остаются в каталоге из вывода. Для одной suite:
|
||||||
|
`node test/sqlite.cjs app.e2e-spec.ts`.
|
||||||
|
|
||||||
| Риск | Проверка реальной DB |
|
| Риск | Проверка реальной DB |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
|||||||
+2
-2
@@ -24,10 +24,10 @@
|
|||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "DATABASE_URL=file:/tmp/wealth-ledger-e2e.db prisma migrate deploy && NODE_ENV=development NODE_OPTIONS=--experimental-vm-modules jest --config ./test/jest-e2e.json",
|
"test:e2e": "node test/sqlite.cjs --all",
|
||||||
"test:sqlite": "node test/sqlite.cjs",
|
"test:sqlite": "node test/sqlite.cjs",
|
||||||
"test:durability": "node test/bank-cards-durability.cjs",
|
"test:durability": "node test/bank-cards-durability.cjs",
|
||||||
"test:openapi-contract": "NODE_OPTIONS=--experimental-vm-modules jest --config ./test/jest-e2e.json --runInBand openapi-contract.e2e-spec.ts",
|
"test:openapi-contract": "node test/sqlite.cjs openapi-contract.e2e-spec.ts",
|
||||||
"verify": "npm run lint && npm run typecheck:app && npm run typecheck:test && npm test -- --runInBand && npm run build"
|
"verify": "npm run lint && npm run typecheck:app && npm run typecheck:test && npm test -- --runInBand && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import { defineConfig } from 'prisma/config';
|
import { defineConfig } from 'prisma/config';
|
||||||
|
|
||||||
|
// Prisma 7.9.1 fresh SQLite migration fails with inherited RUST_LOG=warn/error.
|
||||||
|
// Use the engine default; application logging is configured separately.
|
||||||
|
delete process.env.RUST_LOG;
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
schema: 'prisma/schema.prisma',
|
schema: 'prisma/schema.prisma',
|
||||||
datasource: {
|
datasource: {
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ for (const name of previous) assert.equal(readFileSync(join(baseline, 'prisma/mi
|
|||||||
const Database = require(join(repo, 'node_modules/better-sqlite3'));
|
const Database = require(join(repo, 'node_modules/better-sqlite3'));
|
||||||
const dir = mkdtempSync(join(tmpdir(), 'wealthledger-bank-durability-'));
|
const dir = mkdtempSync(join(tmpdir(), 'wealthledger-bank-durability-'));
|
||||||
let file = join(dir, 'history.db');
|
let file = join(dir, 'history.db');
|
||||||
const env = { ...process.env, DATABASE_URL: `file:${file}`, RUST_LOG: 'info', NODE_ENV: 'development', LOG_LEVEL: 'error', FRONTEND_ORIGIN: 'http://localhost:5189', JWT_SECRET: 'fictional-bank-durability-secret-long-enough', TOKEN_ENCRYPTION_KEY: Buffer.from('0123456789abcdef0123456789abcdef').toString('base64') };
|
const env = { ...process.env, DATABASE_URL: `file:${file}`, NODE_ENV: 'development', LOG_LEVEL: 'error', FRONTEND_ORIGIN: 'http://localhost:5189', JWT_SECRET: 'fictional-bank-durability-secret-long-enough', TOKEN_ENCRYPTION_KEY: Buffer.from('0123456789abcdef0123456789abcdef').toString('base64') };
|
||||||
|
// Historical baseline config predates the Prisma RUST_LOG isolation.
|
||||||
|
delete env.RUST_LOG;
|
||||||
let child, origin, run = 0;
|
let child, origin, run = 0;
|
||||||
const credentials = { email: 'durability@example.test', password: 'fictional password long enough' };
|
const credentials = { email: 'durability@example.test', password: 'fictional password long enough' };
|
||||||
const rub = value => ({ currency: 'RUB', value });
|
const rub = value => ({ currency: 'RUB', value });
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { INestApplication } from '@nestjs/common';
|
|||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { execFileSync } from 'node:child_process';
|
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import type { Server } from 'node:http';
|
import type { Server } from 'node:http';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
@@ -83,23 +82,6 @@ describe('Manual bank cards configured HTTP and real SQLite', () => {
|
|||||||
db = app.get(PrismaService);
|
db = app.get(PrismaService);
|
||||||
};
|
};
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const url = process.env.DATABASE_URL;
|
|
||||||
if (
|
|
||||||
!url ||
|
|
||||||
!/^file:\/(?:private\/)?tmp\/bank-cards-[^/]+\/.+\.db$/.test(url)
|
|
||||||
)
|
|
||||||
throw new Error(
|
|
||||||
'Pass a unique /tmp/bank-cards-*/test.db DATABASE_URL. No shared/user database is allowed.',
|
|
||||||
);
|
|
||||||
execFileSync(
|
|
||||||
process.execPath,
|
|
||||||
['node_modules/prisma/build/index.js', 'migrate', 'deploy'],
|
|
||||||
{
|
|
||||||
cwd: process.cwd(),
|
|
||||||
env: { ...process.env, RUST_LOG: 'info' },
|
|
||||||
stdio: 'pipe',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await start();
|
await start();
|
||||||
await db.user.createMany({
|
await db.user.createMany({
|
||||||
data: [
|
data: [
|
||||||
|
|||||||
+15
-2
@@ -1,4 +1,17 @@
|
|||||||
process.env.DATABASE_URL ??= 'file:/tmp/wealth-ledger-e2e.db';
|
const { join, dirname, basename } = require('node:path');
|
||||||
|
const { tmpdir } = require('node:os');
|
||||||
|
const dir = process.env.WL_E2E_DIRECTORY;
|
||||||
|
if (
|
||||||
|
!dir ||
|
||||||
|
dirname(dir) !== tmpdir() ||
|
||||||
|
!basename(dir).startsWith('wealthledger-e2e-') ||
|
||||||
|
process.env.DATABASE_URL !== `file:${join(dir, 'test.db')}`
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'Use npm run test:e2e or test:sqlite: an isolated runner database is required.',
|
||||||
|
);
|
||||||
|
}
|
||||||
process.env.FRONTEND_ORIGIN = 'http://localhost:5173';
|
process.env.FRONTEND_ORIGIN = 'http://localhost:5173';
|
||||||
process.env.JWT_SECRET = 'e2e-secret-that-is-at-least-thirty-two-characters';
|
process.env.JWT_SECRET = 'e2e-secret-that-is-at-least-thirty-two-characters';
|
||||||
process.env.TOKEN_ENCRYPTION_KEY = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
process.env.TOKEN_ENCRYPTION_KEY =
|
||||||
|
'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=';
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// Full fresh-DB proof, including concurrent runners and failure/signal cleanup.
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const { spawn } = require('node:child_process');
|
||||||
|
const {
|
||||||
|
mkdtempSync,
|
||||||
|
writeFileSync,
|
||||||
|
readFileSync,
|
||||||
|
existsSync,
|
||||||
|
} = require('node:fs');
|
||||||
|
const { tmpdir } = require('node:os');
|
||||||
|
const { join, resolve } = require('node:path');
|
||||||
|
const cwd = resolve(__dirname, '..');
|
||||||
|
const evidence = mkdtempSync(join(tmpdir(), 'wealthledger-sqlite-regression-'));
|
||||||
|
const sentinel = join(evidence, 'user.db');
|
||||||
|
writeFileSync(sentinel, 'fictional user database: must never be opened');
|
||||||
|
const before = readFileSync(sentinel);
|
||||||
|
const databases = new Set();
|
||||||
|
async function check(name, overrides = {}, signal) {
|
||||||
|
const child = spawn(process.execPath, ['test/sqlite.cjs', '--all'], {
|
||||||
|
cwd,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
DATABASE_URL: `file:${sentinel}`,
|
||||||
|
RUST_LOG: 'warn',
|
||||||
|
LOG_LEVEL: 'error',
|
||||||
|
...overrides,
|
||||||
|
},
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
});
|
||||||
|
let log = '';
|
||||||
|
let sent = false;
|
||||||
|
child.stdout.on('data', (chunk) => {
|
||||||
|
log += chunk;
|
||||||
|
if (signal && !sent && log.includes('SQLite ')) {
|
||||||
|
sent = true;
|
||||||
|
child.kill(signal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
child.stderr.on('data', (chunk) => {
|
||||||
|
log += chunk;
|
||||||
|
});
|
||||||
|
const code = await new Promise((resolve, reject) => {
|
||||||
|
child.once('error', reject);
|
||||||
|
child.once('close', resolve);
|
||||||
|
});
|
||||||
|
writeFileSync(join(evidence, `${name}.log`), log);
|
||||||
|
const dirs = [...log.matchAll(/^SQLite .+: (.+)$/gm)].map(
|
||||||
|
(match) => match[1],
|
||||||
|
);
|
||||||
|
assert.ok(dirs.length > 0, 'runner must allocate a database');
|
||||||
|
for (const dir of dirs) {
|
||||||
|
assert.ok(!databases.has(dir), 'runners must never share databases');
|
||||||
|
databases.add(dir);
|
||||||
|
assert.ok(!existsSync(dir), `runner must clean up ${dir}`);
|
||||||
|
}
|
||||||
|
assert.deepEqual(
|
||||||
|
readFileSync(sentinel),
|
||||||
|
before,
|
||||||
|
'user database must be untouched',
|
||||||
|
);
|
||||||
|
const expected =
|
||||||
|
signal === 'SIGINT'
|
||||||
|
? 130
|
||||||
|
: signal === 'SIGTERM'
|
||||||
|
? 143
|
||||||
|
: overrides.PRISMA_SCHEMA_ENGINE_BINARY
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
assert.equal(code, expected, `See ${name}.log`);
|
||||||
|
console.log(`PASS ${name}: ${dirs.length} isolated databases`);
|
||||||
|
}
|
||||||
|
(async () => {
|
||||||
|
console.log(`Evidence: ${evidence}`);
|
||||||
|
for (let i = 1; i <= 3; i++) await check(`sequential-${i}`);
|
||||||
|
const parallel = await Promise.allSettled([
|
||||||
|
check('parallel-1'),
|
||||||
|
check('parallel-2', { RUST_LOG: 'error' }),
|
||||||
|
]);
|
||||||
|
for (const result of parallel)
|
||||||
|
if (result.status === 'rejected') throw result.reason;
|
||||||
|
await check('migration-failure', {
|
||||||
|
PRISMA_SCHEMA_ENGINE_BINARY: join(evidence, 'missing-engine'),
|
||||||
|
});
|
||||||
|
await check('sigint', {}, 'SIGINT');
|
||||||
|
await check('sigterm', {}, 'SIGTERM');
|
||||||
|
console.log(`PASS regression: ${evidence}`);
|
||||||
|
})().catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
||||||
+86
-36
@@ -1,42 +1,92 @@
|
|||||||
const { spawnSync } = require('node:child_process');
|
const { spawn } = require('node:child_process');
|
||||||
const { mkdtempSync, rmSync } = require('node:fs');
|
const { mkdtempSync, readdirSync, rmSync } = require('node:fs');
|
||||||
|
const { tmpdir } = require('node:os');
|
||||||
const { join, resolve } = require('node:path');
|
const { join, resolve } = require('node:path');
|
||||||
|
|
||||||
const cwd = resolve(__dirname, '..');
|
const cwd = resolve(__dirname, '..');
|
||||||
for (const suite of ['app.e2e-spec.ts', 'bank-cards.e2e-spec.ts']) {
|
const available = readdirSync(__dirname)
|
||||||
const dir = mkdtempSync('/tmp/bank-cards-');
|
.filter((name) => name.endsWith('.e2e-spec.ts'))
|
||||||
const env = {
|
.sort();
|
||||||
...process.env,
|
const requested = process.argv.slice(2);
|
||||||
DATABASE_URL: `file:${join(dir, 'test.db')}`,
|
const suites =
|
||||||
RUST_LOG: 'info',
|
requested.length === 0
|
||||||
NODE_ENV: 'development',
|
? ['app.e2e-spec.ts', 'bank-cards.e2e-spec.ts']
|
||||||
NODE_OPTIONS: '--experimental-vm-modules',
|
: requested.length === 1 && requested[0] === '--all'
|
||||||
};
|
? available
|
||||||
|
: requested;
|
||||||
|
if (suites.some((suite) => !available.includes(suite)))
|
||||||
|
throw new Error('Unknown e2e suite');
|
||||||
|
let child;
|
||||||
|
let interrupted;
|
||||||
|
let killTimer;
|
||||||
|
function kill(signal) {
|
||||||
|
if (!child) return;
|
||||||
try {
|
try {
|
||||||
for (const args of [
|
process.kill(-child.pid, signal);
|
||||||
['node_modules/prisma/build/index.js', 'migrate', 'deploy'],
|
} catch (error) {
|
||||||
[
|
if (error.code !== 'ESRCH') throw error;
|
||||||
'node_modules/jest/bin/jest.js',
|
|
||||||
'--config',
|
|
||||||
'test/jest-e2e.json',
|
|
||||||
'--runInBand',
|
|
||||||
'--runTestsByPath',
|
|
||||||
`test/${suite}`,
|
|
||||||
],
|
|
||||||
]) {
|
|
||||||
const result = spawnSync(process.execPath, args, {
|
|
||||||
cwd,
|
|
||||||
env,
|
|
||||||
stdio: 'inherit',
|
|
||||||
});
|
|
||||||
if (result.error) throw result.error;
|
|
||||||
if (result.status !== 0) {
|
|
||||||
process.exitCode = result.status ?? 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
rmSync(dir, { recursive: true, force: true });
|
|
||||||
}
|
}
|
||||||
if (process.exitCode) break;
|
|
||||||
}
|
}
|
||||||
|
for (const signal of ['SIGINT', 'SIGTERM']) {
|
||||||
|
process.on(signal, () => {
|
||||||
|
interrupted = signal;
|
||||||
|
kill(signal);
|
||||||
|
killTimer ??= setTimeout(() => kill('SIGKILL'), 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function run(args, env) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
child = spawn(process.execPath, args, {
|
||||||
|
cwd,
|
||||||
|
env,
|
||||||
|
stdio: 'inherit',
|
||||||
|
detached: true,
|
||||||
|
});
|
||||||
|
child.once('error', reject);
|
||||||
|
child.once('close', (code, signal) => {
|
||||||
|
clearTimeout(killTimer);
|
||||||
|
child = undefined;
|
||||||
|
if (code !== 0 || signal)
|
||||||
|
reject(new Error(`Child failed: ${signal ?? code}`));
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
(async () => {
|
||||||
|
for (const suite of suites) {
|
||||||
|
if (interrupted) throw new Error(interrupted);
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), 'wealthledger-e2e-'));
|
||||||
|
const env = {
|
||||||
|
...process.env,
|
||||||
|
DATABASE_URL: `file:${join(dir, 'test.db')}`,
|
||||||
|
WL_E2E_DIRECTORY: dir,
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
NODE_OPTIONS: '--experimental-vm-modules',
|
||||||
|
};
|
||||||
|
console.log(`SQLite ${suite}: ${dir}`);
|
||||||
|
try {
|
||||||
|
await run(
|
||||||
|
['node_modules/prisma/build/index.js', 'migrate', 'deploy'],
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
if (interrupted) throw new Error(interrupted);
|
||||||
|
await run(
|
||||||
|
[
|
||||||
|
'node_modules/jest/bin/jest.js',
|
||||||
|
'--config',
|
||||||
|
'test/jest-e2e.json',
|
||||||
|
'--runInBand',
|
||||||
|
'--runTestsByPath',
|
||||||
|
`test/${suite}`,
|
||||||
|
],
|
||||||
|
env,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})().catch((error) => {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exitCode =
|
||||||
|
interrupted === 'SIGINT' ? 130 : interrupted === 'SIGTERM' ? 143 : 1;
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user