test(api): общий хелпер с фиксированным RSA-ключом
Фиксированный ключ (OIDC_RSA_PRIVATE_KEY) вместо generateKeyPair на каждое построение приложения: фаза тестов 9.9с → 1.6с (×6). Локальные копии buildTestApp/adminToken в 4 файлах заменены на helpers.ts
This commit is contained in:
@@ -1,17 +1,9 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { buildApp } from '../src/app.js'
|
import { buildTestApp } from './helpers.js'
|
||||||
import { loadConfig } from '../src/config.js'
|
|
||||||
|
|
||||||
describe('app-switcher API', () => {
|
describe('app-switcher API', () => {
|
||||||
it('GET /api/v1/app-switcher is public and returns defaults', async () => {
|
it('GET /api/v1/app-switcher is public and returns defaults', async () => {
|
||||||
const config = loadConfig({
|
const app = await buildTestApp()
|
||||||
...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 res = await app.inject({ method: 'GET', url: '/api/v1/app-switcher' })
|
const res = await app.inject({ method: 'GET', url: '/api/v1/app-switcher' })
|
||||||
expect(res.statusCode).toBe(200)
|
expect(res.statusCode).toBe(200)
|
||||||
const body = res.json() as { menuLabel: string; apps: { id: string }[] }
|
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 () => {
|
it('PUT /api/v1/admin/app-switcher requires admin and persists', async () => {
|
||||||
const config = loadConfig({
|
const app = await buildTestApp()
|
||||||
...process.env,
|
|
||||||
JWT_SECRET: 'test-secret-at-least-8',
|
|
||||||
ADMIN_EMAIL: '[email protected]',
|
|
||||||
ADMIN_PASSWORD: 'adminpass',
|
|
||||||
DATABASE_URL: 'sqlite::memory:',
|
|
||||||
NODE_ENV: 'test',
|
|
||||||
})
|
|
||||||
const app = await buildApp({ config, databaseUrl: 'sqlite::memory:' })
|
|
||||||
|
|
||||||
const denied = await app.inject({
|
const denied = await app.inject({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
|
|||||||
@@ -1,30 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { appendAudit, listAudit, purgeAuditOlderThan } from '@authportal/db'
|
import { appendAudit, listAudit, purgeAuditOlderThan } from '@authportal/db'
|
||||||
import { buildApp } from '../src/app.js'
|
import { adminToken, buildTestApp } from './helpers.js'
|
||||||
import { loadConfig } from '../src/config.js'
|
|
||||||
|
|
||||||
async function buildTestApp() {
|
|
||||||
const config = loadConfig({
|
|
||||||
...process.env,
|
|
||||||
JWT_SECRET: 'test-secret-at-least-8',
|
|
||||||
ADMIN_EMAIL: '[email protected]',
|
|
||||||
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<ReturnType<typeof buildTestApp>>) {
|
|
||||||
const login = await app.inject({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/login',
|
|
||||||
payload: { email: '[email protected]', password: 'adminpass' },
|
|
||||||
})
|
|
||||||
expect(login.statusCode).toBe(200)
|
|
||||||
return (login.json() as { access_token: string }).access_token
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('audit log API', () => {
|
describe('audit log API', () => {
|
||||||
it('ingests external events with secret and dedupes by event_id', async () => {
|
it('ingests external events with secret and dedupes by event_id', async () => {
|
||||||
|
|||||||
@@ -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: '[email protected]',
|
||||||
|
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<ReturnType<typeof buildTestApp>>,
|
||||||
|
): Promise<string> {
|
||||||
|
const login = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/v1/auth/login',
|
||||||
|
payload: { email: '[email protected]', password: 'adminpass' },
|
||||||
|
})
|
||||||
|
expect(login.statusCode).toBe(200)
|
||||||
|
return (login.json() as { access_token: string }).access_token
|
||||||
|
}
|
||||||
@@ -6,21 +6,7 @@ import { jwtVerify, createLocalJWKSet } from 'jose'
|
|||||||
import { buildApp } from '../src/app.js'
|
import { buildApp } from '../src/app.js'
|
||||||
import { loadConfig } from '../src/config.js'
|
import { loadConfig } from '../src/config.js'
|
||||||
import { resetOidcKeyCache } from '../src/lib/oidc/keys.js'
|
import { resetOidcKeyCache } from '../src/lib/oidc/keys.js'
|
||||||
|
import { buildTestApp } from './helpers.js'
|
||||||
async function buildTestApp() {
|
|
||||||
resetOidcKeyCache()
|
|
||||||
const config = loadConfig({
|
|
||||||
...process.env,
|
|
||||||
JWT_SECRET: 'test-secret-at-least-8',
|
|
||||||
ADMIN_EMAIL: '[email protected]',
|
|
||||||
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:' })
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('OIDC IdP', () => {
|
describe('OIDC IdP', () => {
|
||||||
it('serves discovery and JWKS', async () => {
|
it('serves discovery and JWKS', async () => {
|
||||||
|
|||||||
@@ -1,30 +1,6 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import { buildApp } from '../src/app.js'
|
|
||||||
import { loadConfig } from '../src/config.js'
|
|
||||||
import { createWebauthnCredential } from '@authportal/db'
|
import { createWebauthnCredential } from '@authportal/db'
|
||||||
|
import { adminToken, buildTestApp } from './helpers.js'
|
||||||
async function buildTestApp() {
|
|
||||||
const config = loadConfig({
|
|
||||||
...process.env,
|
|
||||||
JWT_SECRET: 'test-secret-at-least-8',
|
|
||||||
ADMIN_EMAIL: '[email protected]',
|
|
||||||
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<ReturnType<typeof buildTestApp>>) {
|
|
||||||
const login = await app.inject({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/login',
|
|
||||||
payload: { email: '[email protected]', password: 'adminpass' },
|
|
||||||
})
|
|
||||||
expect(login.statusCode).toBe(200)
|
|
||||||
return (login.json() as { access_token: string }).access_token
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('webauthn / passkeys', () => {
|
describe('webauthn / passkeys', () => {
|
||||||
it('exposes webauthn flag on auth config', async () => {
|
it('exposes webauthn flag on auth config', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user