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$/) })