Files
lexgo/admin/tests/audit-logs.test.mjs

105 lines
5.0 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { createSession } from '../src/session.mjs'
import * as audit from '../src/audit-logs.mjs'
function setup() {
const pending = []
const session = createSession({
storage: { getItem() {}, setItem() {}, removeItem() {} },
fetch: (url, options) => new Promise(resolve => pending.push({ url, options, resolve }))
})
session.state.user = { id: 1, role: 'admin', username: 'fixture.admin' }
session.state.token = 'fictional-token'
return { session, pending }
}
const response = data => ({ ok: true, status: 200, json: async () => ({ code: 200, data }) })
test('unclassified rejected account updates have a readable action and filter', () => {
assert.equal(audit.actionLabels.update, '更新账号')
assert.equal(new URLSearchParams(audit.auditQuery('operation', { action: 'update' })).get('action'), 'update')
})
test('audit query normalizes exact username, preserves RFC3339 bounds and bounds pagination', async () => {
const { session, pending } = setup()
assert.equal(typeof session.queryAuditLogs, 'function')
const request = session.queryAuditLogs('operation', { username: ' Fixture.Admin ', result: 'failure', action: 'reset_password', page: 2, limit: 100, from: new Date('2026-09-10T00:00:00+08:00'), to: new Date('2026-09-11T00:00:00+08:00') })
const url = new URL(pending[0].url, 'https://fixture.invalid')
assert.equal(url.pathname, '/api/v1/operation-logs')
assert.deepEqual(Object.fromEntries(url.searchParams), { page: '2', limit: '100', username: 'fixture.admin', result: 'failure', action: 'reset_password', from: '2026-09-09T16:00:00.000Z', to: '2026-09-10T16:00:00.000Z' })
assert.equal(pending[0].options.cache, 'no-store')
pending[0].resolve(response({ items: [], total: 0, page: 2, limit: 100 }))
assert.equal((await request).total, 0)
for (const filter of [{ limit: 101 }, { page: 0 }, { result: 'unknown' }, { action: 'delete' }, { from: 'bad' }, { from: '2026-09-11', to: '2026-09-10' }]) {
await assert.rejects(session.queryAuditLogs('operation', filter))
}
assert.equal(pending.length, 1)
})
test('login query omits empty filters and rejects operation-only actions and unauthenticated access', async () => {
const { session, pending } = setup()
assert.equal(typeof session.queryAuditLogs, 'function')
const request = session.queryAuditLogs('login', { username: ' ', result: '', from: null, to: null })
assert.equal(pending[0].url, '/api/v1/login-logs?page=1&limit=20')
pending[0].resolve(response({ items: [], total: 0, page: 1, limit: 20 }))
await request
await assert.rejects(session.queryAuditLogs('login', { action: 'create' }))
session.clear()
await assert.rejects(session.queryAuditLogs('login'), /请先登录/)
assert.equal(pending.length, 1)
})
test('late audit response cannot survive session change', async () => {
const { session, pending } = setup()
assert.equal(typeof session.queryAuditLogs, 'function')
const request = session.queryAuditLogs('login')
session.clear()
session.state.user = { id: 3, role: 'admin', username: 'fixture.otheradmin' }
pending[0].resolve(response({ items: [{ username: 'fixture.private' }], total: 1 }))
await assert.rejects(request, /会话已变化/)
})
test('route/filter changes discard out-of-order rows and invalidation clears loaded private data', async () => {
assert.equal(typeof audit.createAuditLogLoader, 'function')
const { session, pending } = setup()
const state = { items: [], total: 0, loading: false, error: '' }
const loader = audit.createAuditLogLoader(session, state)
const old = loader.load('login', {})
const current = loader.load('operation', {})
pending[1].resolve(response({ items: [{ id: 2 }], total: 1 }))
await current
pending[0].resolve(response({ items: [{ id: 1 }], total: 1 }))
await old
assert.deepEqual(state.items, [{ id: 2 }])
loader.invalidate()
assert.deepEqual(state.items, [])
assert.equal(state.total, 0)
const late = loader.load('login', {})
session.clear()
pending[2].resolve(response({ items: [{ id: 3 }], total: 1 }))
await late
assert.deepEqual(state.items, [])
assert.equal(state.error, '')
})
test('loading page two preserves total so pagination cannot clamp the page back to one', async () => {
const { session, pending } = setup()
const state = { items: [], total: 0, loading: false, error: '' }
const loader = audit.createAuditLogLoader(session, state)
const first = loader.load('login', { page: 1 })
pending[0].resolve(response({ items: [{ id: 50 }], total: 50 }))
await first
const second = loader.load('login', { page: 2 })
assert.equal(state.total, 50)
assert.equal(state.loading, true)
assert.deepEqual(state.items, [])
assert.equal(new URL(pending[1].url, 'https://fixture.invalid').searchParams.get('page'), '2')
pending[1].resolve(response({ items: [{ id: 30 }], total: 50 }))
await second
assert.deepEqual(state.items, [{ id: 30 }])
assert.equal(state.total, 50)
loader.invalidate()
assert.equal(state.total, 0)
assert.deepEqual(state.items, [])
})