From e135150c2d36f99cccf113f84c15058f0a54fc64 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Wed, 23 Sep 2026 14:51:38 +0700 Subject: [PATCH] =?UTF-8?q?test(api):=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=85=D0=B5=D0=BB=D0=BF=D0=B5=D1=80=20=D1=81=20=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=81=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=BC=20RSA-=D0=BA=D0=BB=D1=8E=D1=87=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Фиксированный ключ (OIDC_RSA_PRIVATE_KEY) вместо generateKeyPair на каждое построение приложения: фаза тестов 9.9с → 1.6с (×6). Локальные копии buildTestApp/adminToken в 4 файлах заменены на helpers.ts --- apps/api/test/app-switcher.test.ts | 22 ++-------- apps/api/test/audit.test.ts | 26 +----------- apps/api/test/helpers.ts | 67 ++++++++++++++++++++++++++++++ apps/api/test/oidc.test.ts | 16 +------ apps/api/test/webauthn.test.ts | 26 +----------- 5 files changed, 73 insertions(+), 84 deletions(-) create mode 100644 apps/api/test/helpers.ts diff --git a/apps/api/test/app-switcher.test.ts b/apps/api/test/app-switcher.test.ts index 09fa348..f85178d 100644 --- a/apps/api/test/app-switcher.test.ts +++ b/apps/api/test/app-switcher.test.ts @@ -1,17 +1,9 @@ import { describe, expect, it } from 'vitest' -import { buildApp } from '../src/app.js' -import { loadConfig } from '../src/config.js' +import { buildTestApp } from './helpers.js' describe('app-switcher API', () => { it('GET /api/v1/app-switcher is public and returns defaults', async () => { - const config = loadConfig({ - ...process.env, - JWT_SECRET: 'test-secret-at-least-8', - ADMIN_PASSWORD: 'admin', - DATABASE_URL: 'sqlite::memory:', - NODE_ENV: 'test', - }) - const app = await buildApp({ config, databaseUrl: 'sqlite::memory:' }) + const app = await buildTestApp() const res = await app.inject({ method: 'GET', url: '/api/v1/app-switcher' }) expect(res.statusCode).toBe(200) const body = res.json() as { menuLabel: string; apps: { id: string }[] } @@ -38,15 +30,7 @@ describe('app-switcher API', () => { }) it('PUT /api/v1/admin/app-switcher requires admin and persists', async () => { - const config = loadConfig({ - ...process.env, - JWT_SECRET: 'test-secret-at-least-8', - ADMIN_EMAIL: 'admin@test.local', - ADMIN_PASSWORD: 'adminpass', - DATABASE_URL: 'sqlite::memory:', - NODE_ENV: 'test', - }) - const app = await buildApp({ config, databaseUrl: 'sqlite::memory:' }) + const app = await buildTestApp() const denied = await app.inject({ method: 'PUT', diff --git a/apps/api/test/audit.test.ts b/apps/api/test/audit.test.ts index ee1750e..06d80b0 100644 --- a/apps/api/test/audit.test.ts +++ b/apps/api/test/audit.test.ts @@ -1,30 +1,6 @@ import { describe, expect, it } from 'vitest' import { appendAudit, listAudit, purgeAuditOlderThan } from '@authportal/db' -import { buildApp } from '../src/app.js' -import { loadConfig } from '../src/config.js' - -async function buildTestApp() { - const config = loadConfig({ - ...process.env, - JWT_SECRET: 'test-secret-at-least-8', - ADMIN_EMAIL: 'admin@test.local', - ADMIN_PASSWORD: 'adminpass', - DATABASE_URL: 'sqlite::memory:', - NODE_ENV: 'test', - AUDIT_INGEST_SECRET: 'dev-audit-ingest-secret', - }) - return buildApp({ config, databaseUrl: 'sqlite::memory:' }) -} - -async function adminToken(app: Awaited>) { - const login = await app.inject({ - method: 'POST', - url: '/api/v1/auth/login', - payload: { email: 'admin@test.local', password: 'adminpass' }, - }) - expect(login.statusCode).toBe(200) - return (login.json() as { access_token: string }).access_token -} +import { adminToken, buildTestApp } from './helpers.js' describe('audit log API', () => { it('ingests external events with secret and dedupes by event_id', async () => { diff --git a/apps/api/test/helpers.ts b/apps/api/test/helpers.ts new file mode 100644 index 0000000..4512795 --- /dev/null +++ b/apps/api/test/helpers.ts @@ -0,0 +1,67 @@ +import { expect } from 'vitest' +import { buildApp } from '../src/app.js' +import { loadConfig } from '../src/config.js' +import { resetOidcKeyCache } from '../src/lib/oidc/keys.js' + +/** Фиксированный тестовый RSA-ключ: importPKCS8 (~мс) вместо generateKeyPair + * (~100–300 мс) при построении приложения в каждом тесте. */ +const TEST_OIDC_RSA_PRIVATE_KEY = [ + '-----BEGIN PRIVATE KEY-----', + 'MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCt/+Vwi6txs4ET', + '/OczfcOX0mVCOfxT3qGejj1xX19A4gKeGGjQ5LSBDGzcztncYMd0J/H1ozUWn0NU', + 'ctolNRSTFchAYj1MoC2Pacfhe445nLR4bOssdADO9bwhHmWy8WQFyYc/lqs/xPSd', + 'haZFwDRBRRfh9uxzzUPR8FXCovHEJG5HiLuvmALL1W7nkDg+L+vyLYCNje482hS+', + 'ZcW3IH17gQpXgpqgXPaGHfZT1teIiYwhelaqPsApMQroOisKYGoQi1z/G/mdvaOF', + '/jravyBqhr5Hm03lFOMBvMptGrXsjopy+BVlqBVcvdwdyFfz94dayhnCmfEIvK7W', + 'ynYGtoLnAgMBAAECggEAKqld04DjOb8FzGVLOKRCwyQ6YIkAFX42CT6d0QDNxYZE', + 'lwesF1+j5CosPN1YrEBsVs3+dLgd6ZyF8Rwx4UKI92E1VpffqLxzP0Dz2ZSmM/j4', + 'hLhL1vvgQN4eUM53p+6olASr7QrflQSQ51hgAqUUCtbKesREiEYRNVWkaVLzTSAr', + 'KrwWAF81hzEms0KuHw6/EgThBkWYGAMzZjECFa173yBHklt1ikdB1AOmieL9PZbB', + 'anjIequkUE2yoTJM/wOd2GN7sy61PmnMApwl1ke6k4VhrJeQhfahck0D2ynDvWqX', + 'fnn1Wr/BRmSi38RNEOySWkrCVjM0XSjO3wEuHFFyuQKBgQDwgOgzgL1W5b13LKXo', + '8F/AJNRmi4vpM1lftMRalx5NU5ugw1iQ2zJzLG3Tl1/nvyt8tGFkXB7Dw9M9H0as', + 'jjk7D8ZzL1a7COWQx7wya1l8vMxwv40ERvoWypwpuVo12SvRnHWgGwsbqW4wxYIG', + '9HsdvbxglFiVcSq+b6FDdg2nyQKBgQC5NgKatFPPed/DFb6MCkW+5Wnw2HzdKHxD', + 'fEkAJeb3xX+9k7OifRHppurwaDV4jUPJ5W1Whly77cg+xmEYAbUdPS1kgfYJaDn1', + 'mPN9dI7RP15v4X46/+5v5EZYAKcdUdMzr9gJ2iQVpoDi5ojp+LJknPLBuo7XTfp3', + 'kj9lifONLwKBgHa3SudRXyTr1rBjC6qGLN2N0IKl03RTcoR5mIsXGVAWOzYJ90fZ', + 'y3dqsPPHPwIILZErAQAnMNB1c2oybBJ1Se7LhKwK7Ab7B0aGMXG557xY/ZmSFCvO', + 'VVi2vosiAVOfVa/sSWu0rbED226br+l6+OLfa4NI9skdyXmjZLkGJwtxAoGBALDp', + 'LkOnzAZ6W4p00rXheg2GBVPQ9jQp38h2hyRlD+HL/OCQnR2wadPNZMyOPcUQ6peH', + 'VpE3dozr7EJKK6RKvT1YmmtO2DzUGcjGeI51L5Yf+YzUz0Z8AL1QRwD4Xqha6GqO', + 'pMWEYMNtb99MZatXqs8AO8Fg+eAe1Agy4euTgdhxAoGBAPAgrLUl7uJMdCsrHFEG', + 'K8CH3BZwz2ytSj6429IdX4OCzkkmdsWnjx5/FK2hc+fXMluhybaIziSntgs55vzZ', + '1hd/Xw1T2FSTS6BuWegxVtjO6BQjbVXrxOumA0PPmFYaGMPvvBQrbZy8OSeuaKD/', + 'a9jKZApDKnT8vmYvmyMMtsmE', + '-----END PRIVATE KEY-----', +].join('\n') + +export async function buildTestApp(env: NodeJS.ProcessEnv = {}) { + resetOidcKeyCache() + const config = loadConfig({ + ...process.env, + JWT_SECRET: 'test-secret-at-least-8', + ADMIN_EMAIL: 'admin@test.local', + ADMIN_PASSWORD: 'adminpass', + DATABASE_URL: 'sqlite::memory:', + ISSUER: 'https://auth.test.local', + OIDC_ISSUER: 'https://auth.test.local', + NODE_ENV: 'test', + AUDIT_INGEST_SECRET: 'dev-audit-ingest-secret', + OIDC_RSA_PRIVATE_KEY: TEST_OIDC_RSA_PRIVATE_KEY, + ...env, + }) + return buildApp({ config, databaseUrl: 'sqlite::memory:' }) +} + +export async function adminToken( + app: Awaited>, +): Promise { + const login = await app.inject({ + method: 'POST', + url: '/api/v1/auth/login', + payload: { email: 'admin@test.local', password: 'adminpass' }, + }) + expect(login.statusCode).toBe(200) + return (login.json() as { access_token: string }).access_token +} diff --git a/apps/api/test/oidc.test.ts b/apps/api/test/oidc.test.ts index 3c51f9e..3db9817 100644 --- a/apps/api/test/oidc.test.ts +++ b/apps/api/test/oidc.test.ts @@ -6,21 +6,7 @@ import { jwtVerify, createLocalJWKSet } from 'jose' import { buildApp } from '../src/app.js' import { loadConfig } from '../src/config.js' import { resetOidcKeyCache } from '../src/lib/oidc/keys.js' - -async function buildTestApp() { - resetOidcKeyCache() - const config = loadConfig({ - ...process.env, - JWT_SECRET: 'test-secret-at-least-8', - ADMIN_EMAIL: 'admin@test.local', - ADMIN_PASSWORD: 'adminpass', - DATABASE_URL: 'sqlite::memory:', - ISSUER: 'https://auth.test.local', - OIDC_ISSUER: 'https://auth.test.local', - NODE_ENV: 'test', - }) - return buildApp({ config, databaseUrl: 'sqlite::memory:' }) -} +import { buildTestApp } from './helpers.js' describe('OIDC IdP', () => { it('serves discovery and JWKS', async () => { diff --git a/apps/api/test/webauthn.test.ts b/apps/api/test/webauthn.test.ts index e032bd4..964b7ee 100644 --- a/apps/api/test/webauthn.test.ts +++ b/apps/api/test/webauthn.test.ts @@ -1,30 +1,6 @@ import { describe, expect, it } from 'vitest' -import { buildApp } from '../src/app.js' -import { loadConfig } from '../src/config.js' import { createWebauthnCredential } from '@authportal/db' - -async function buildTestApp() { - const config = loadConfig({ - ...process.env, - JWT_SECRET: 'test-secret-at-least-8', - ADMIN_EMAIL: 'admin@test.local', - ADMIN_PASSWORD: 'adminpass', - DATABASE_URL: 'sqlite::memory:', - ISSUER: 'https://auth.test.local', - NODE_ENV: 'test', - }) - return buildApp({ config, databaseUrl: 'sqlite::memory:' }) -} - -async function adminToken(app: Awaited>) { - const login = await app.inject({ - method: 'POST', - url: '/api/v1/auth/login', - payload: { email: 'admin@test.local', password: 'adminpass' }, - }) - expect(login.statusCode).toBe(200) - return (login.json() as { access_token: string }).access_token -} +import { adminToken, buildTestApp } from './helpers.js' describe('webauthn / passkeys', () => { it('exposes webauthn flag on auth config', async () => {