ituoke/frontend/tests/logs.test.mjs

84 lines
3.6 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { runInNewContext } from 'node:vm'
import * as Vue from 'vue'
import ts from 'typescript'
const source = readFileSync(new URL('../src/lib/useLogs.ts', import.meta.url), 'utf8')
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText
const settle = async () => { for (let i = 0; i < 60; i++) await Promise.resolve() }
function harness(disk = { lines: ['previous run'], fail: false, failRead: false }) {
const module = { exports: {} }
const api = {
async Read() { if (disk.beforeRead) await disk.beforeRead(); if (disk.failRead) throw Error('read error'); return [...disk.lines] },
async Append(line) { if (disk.fail) throw Error('disk full'); disk.lines.push(line) },
async Clear() { if (disk.beforeClear) await disk.beforeClear(); if (disk.failClear) throw Error('clear error'); disk.lines = [] },
}
runInNewContext(compiled, { module, exports: module.exports, require: name => name === 'vue' ? Vue : { Service: api }, Date })
return { store: module.exports.useLogs(), disk }
}
test('startup merges historical logs and early events exactly once, then restores after restart', async () => {
const h = harness()
let release
h.disk.beforeRead = () => new Promise(resolve => { release = resolve })
h.store.log('app boot')
await settle()
h.store.log('devices: 1')
release(); await h.store.load()
assert.equal(h.store.lines.value.length, 3)
assert.equal(h.store.lines.value[0], 'previous run')
assert.match(h.disk.lines[1], /^\[\d{4}-\d{2}-\d{2} .*\] app boot$/)
assert.match(h.disk.lines[2], /devices: 1$/)
delete h.disk.beforeRead
const restarted = harness(h.disk); await restarted.store.load()
assert.deepEqual([...restarted.store.lines.value], h.disk.lines)
})
test('failed reads preserve history; failed writes remain pending and retry in order', async () => {
const h = harness()
h.disk.failRead = true
h.store.log('one'); await h.store.load()
assert.match(h.store.error.value, /read error/)
assert.equal(h.disk.lines.length, 1)
h.disk.failRead = false; h.disk.fail = true
h.store.log('two'); await h.store.load()
assert.match(h.store.error.value, /disk full/)
h.disk.fail = false; await h.store.load()
assert.equal(h.store.error.value, '')
assert.equal(h.disk.lines.length, 3)
assert.match(h.disk.lines[1], /one$/)
assert.match(h.disk.lines[2], /two$/)
})
test('clearing deletes persisted history but keeps events arriving during clear', async () => {
const h = harness()
h.store.log('old'); await h.store.load()
let release
h.disk.beforeClear = () => new Promise(resolve => { release = resolve })
const clearing = h.store.clear(); await settle()
h.store.log('new\nmessage')
release(); await clearing; await h.store.load()
assert.equal(h.store.busy.value, false)
assert.equal(h.disk.lines.length, 1)
assert.match(h.disk.lines[0], /new\\nmessage$/)
assert.deepEqual([...h.store.lines.value], h.disk.lines)
})
test('retry repeats a failed clear and oversized errors do not block later events', async () => {
const h = harness(); await h.store.load()
h.disk.failClear = true
await h.store.clear()
assert.match(h.store.error.value, /clear error/)
assert.deepEqual(h.disk.lines, ['previous run'])
h.disk.failClear = false
await h.store.retry()
assert.equal(h.disk.lines.length, 0)
h.store.log('长'.repeat(70000)); h.store.log('next event'); await h.store.load()
assert.equal(h.disk.lines.length, 2)
assert.ok(Buffer.byteLength(h.disk.lines[0]) < 65536)
assert.match(h.disk.lines[1], /next event$/)
})