Files
accounted/lib/__tests__/logger.test.ts
T
admin e7f0070e84
masterplan-lock / check (push) Successful in 19s
ci / sonar (push) Failing after 21s
security / scan (push) Failing after 1m37s
fix(restore): återställ default branch-filer som raderades av docs-publiceringen (7e33543)
Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i
server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma
strängvärde byggs med konkatenering i stället för att matcha mönstret.
2026-11-10 09:37:22 +01:00

94 lines
3.1 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import { createTestLogger } from '../logger'
// The sink element type is the (unexported) LogRecord from lib/logger.ts,
// recovered via Parameters<> so the test stays in sync with the real signature.
type Sink = Parameters<typeof createTestLogger>[1]
describe('logger', () => {
it('emits records with module + msg + level + ts', () => {
const sink: Sink = []
const log = createTestLogger('test/module', sink)
log.info('hello')
expect(sink).toHaveLength(1)
expect(sink[0]).toMatchObject({
level: 'info',
module: 'test/module',
msg: 'hello',
})
expect(typeof sink[0].ts).toBe('string')
})
it('merges base context into every record', () => {
const sink: Sink = []
const log = createTestLogger('m', sink, { requestId: 'req_1' })
log.info('hi')
expect(sink[0].requestId).toBe('req_1')
})
it('child() returns a logger that merges extra context', () => {
const sink: Sink = []
const log = createTestLogger('m', sink, { requestId: 'req_1' })
const child = log.child({ companyId: 'co_1', userId: 'u_1' })
child.warn('oops')
expect(sink[0]).toMatchObject({
requestId: 'req_1',
companyId: 'co_1',
userId: 'u_1',
})
})
it('treats Error args as the err field with name/message/code', () => {
const sink: Sink = []
const log = createTestLogger('m', sink)
const err = new Error('boom')
;(err as Error & { code?: string }).code = '23505'
log.error('insert failed', err)
expect(sink[0].err).toMatchObject({ name: 'Error', message: 'boom', code: '23505' })
})
it('merges plain-object args into context', () => {
const sink: Sink = []
const log = createTestLogger('m', sink)
log.info('done', { durationMs: 42, status: 200 })
expect(sink[0]).toMatchObject({ durationMs: 42, status: 200 })
})
it('redacts sensitive keys recursively', () => {
const sink: Sink = []
const log = createTestLogger('m', sink)
log.info('login', {
user: 'alice',
headers: { authorization: 'Bearer secret', cookie: 'sess=xxx' },
payload: { password: 'hunter2', token: 'tok' },
})
const rec = sink[0] as {
user: string
headers: { authorization: string; cookie: string }
payload: { password: string; token: string }
}
expect(rec.headers.authorization).toBe('[REDACTED]')
expect(rec.headers.cookie).toBe('[REDACTED]')
expect(rec.payload.password).toBe('[REDACTED]')
expect(rec.payload.token).toBe('[REDACTED]')
expect(rec.user).toBe('alice')
})
it('redacts personnummer-shaped strings while preserving UUIDs', () => {
const sink: Sink = []
const log = createTestLogger('m', sink)
log.info('processing for 800101-1234', { uuid: '57484518-3409-4b29-9d23-5d22f08bda63' })
expect(sink[0].msg).toBe('[REDACTED]')
expect(sink[0].uuid).toBe('57484518-3409-4b29-9d23-5d22f08bda63')
})
it('routes non-object, non-Error args into details', () => {
const sink: Sink = []
const log = createTestLogger('m', sink)
log.warn('legacy', 'string arg', 42)
expect(sink[0].details).toEqual(['string arg', 42])
})
})