101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { afterAll, describe, expect, it } from 'vitest'
|
||
import {
|
||
appendAuditBatch,
|
||
closeDb,
|
||
createDb,
|
||
createUser,
|
||
getUserApps,
|
||
getUserById,
|
||
listActiveSessions,
|
||
listAudit,
|
||
migrateSchema,
|
||
runHousekeeping,
|
||
searchDirectoryUsers,
|
||
} from '../../src/index.js'
|
||
|
||
const { db, sqlite } = createDb('sqlite::memory:')
|
||
migrateSchema(sqlite)
|
||
afterAll(() => closeDb(sqlite))
|
||
|
||
function timed<T>(fn: () => T): { ms: number; value: T } {
|
||
const t0 = performance.now()
|
||
const value = fn()
|
||
return { ms: performance.now() - t0, value }
|
||
}
|
||
|
||
// Бюджеты щедрые (CI-раннеры шумные): ловим деградацию на порядок, а не на проценты.
|
||
describe('бюджеты производительности БД', () => {
|
||
const users = Array.from({ length: 200 }, (_, i) =>
|
||
createUser(db, {
|
||
email: `u${i}@x.io`,
|
||
name: `User ${i}`,
|
||
passwordHash: 'h',
|
||
isAdmin: false,
|
||
}),
|
||
)
|
||
|
||
it('вставка 100k сессий и фильтр активных по пользователю < 150 мс', () => {
|
||
const ins = sqlite.prepare(
|
||
`INSERT INTO refresh_sessions (id,user_id,token_hash,expires_at,created_at)
|
||
VALUES (?,?,?,?,?)`,
|
||
)
|
||
const future = new Date(Date.now() + 86_400_000).toISOString()
|
||
const past = '2000-01-01T00:00:00.000Z'
|
||
sqlite.transaction(() => {
|
||
for (let i = 0; i < 100_000; i++) {
|
||
ins.run(`s${i}`, users[i % users.length]!.id, `h${i}`, i % 10 === 0 ? future : past, `t${i}`)
|
||
}
|
||
})()
|
||
listActiveSessions(db, { userId: users[0]!.id }) // прогрев
|
||
const { ms, value } = timed(() => listActiveSessions(db, { userId: users[0]!.id }))
|
||
console.log(`listActiveSessions(user) на 100k: ${ms.toFixed(1)} мс, ${value.length} строк`)
|
||
expect(value.length).toBeGreaterThan(0)
|
||
expect(ms).toBeLessThan(150)
|
||
})
|
||
|
||
it('housekeeping 100k истёкших сессий < 3 с', () => {
|
||
const { ms, value } = timed(() => runHousekeeping(db, { retentionDays: 1 }))
|
||
console.log(`runHousekeeping: ${ms.toFixed(0)} мс, удалено ${value.sessions}`)
|
||
expect(value.sessions).toBeGreaterThan(80_000)
|
||
expect(ms).toBeLessThan(3000)
|
||
})
|
||
|
||
it('пакетный ingest 10k событий одной транзакцией < 1.5 с', () => {
|
||
const events = Array.from({ length: 10_000 }, (_, i) => ({
|
||
eventId: `e${i}`,
|
||
action: i % 2 ? 'auth.login' : 'user.update',
|
||
summary: 'bench',
|
||
}))
|
||
const { ms, value } = timed(() => appendAuditBatch(db, events))
|
||
console.log(`appendAuditBatch(10k): ${ms.toFixed(0)} мс`)
|
||
expect(value.accepted).toBe(10_000)
|
||
expect(ms).toBeLessThan(1500)
|
||
})
|
||
|
||
it('listAudit по kind использует индекс: < 50 мс на 10k', () => {
|
||
listAudit(db, { kind: 'logins', limit: 100 })
|
||
const { ms, value } = timed(() => listAudit(db, { kind: 'logins', limit: 100 }))
|
||
console.log(`listAudit(kind=logins): ${ms.toFixed(1)} мс`)
|
||
expect(value.length).toBe(100)
|
||
expect(ms).toBeLessThan(50)
|
||
})
|
||
|
||
it('10k горячих запросов профиля (prepared) < 400 мс', () => {
|
||
const id = users[0]!.id
|
||
const { ms } = timed(() => {
|
||
for (let i = 0; i < 10_000; i++) {
|
||
getUserById(db, id)
|
||
getUserApps(db, id)
|
||
}
|
||
})
|
||
console.log(`20k prepared-запросов: ${ms.toFixed(0)} мс`)
|
||
expect(ms).toBeLessThan(400)
|
||
})
|
||
|
||
it('поиск каталога по 200 пользователям < 20 мс', () => {
|
||
searchDirectoryUsers(db, 'user 1')
|
||
const { ms } = timed(() => searchDirectoryUsers(db, 'user 1'))
|
||
expect(ms).toBeLessThan(20)
|
||
})
|
||
})
|