Read chat logs incrementally and drain retention (Refs #62)
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { newFixture, type Fixture } from '../helpers/fixture.js';
|
||||
|
||||
let fixture: Fixture;
|
||||
beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); });
|
||||
afterEach(async () => { await fixture.cleanup(); });
|
||||
|
||||
async function setup() {
|
||||
const api = await import('../../src/lib/server/hosts/index.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const host = api.createHost({ name: 'Log host' });
|
||||
const operation = api.queueHostOperation(host.id, 'logs.read', { path: 'logs/games_sp.log' });
|
||||
return { ...api, host, operation, db };
|
||||
}
|
||||
|
||||
describe('transient log results', () => {
|
||||
it.each([
|
||||
{ lines: ['private log body'], size: 17, mtime: '2026-10-08T12:00:00Z', truncated: false },
|
||||
{ contentBase64: Buffer.from('private log body').toString('base64'), size: 17, reset: false, cursor: { offset: 17 } }
|
||||
])('delivers full replies to current readers while storing only metadata', async result => {
|
||||
const api = await setup();
|
||||
const first = api.waitHostOperationStatus(api.operation.id);
|
||||
const second = api.waitHostOperation(api.operation.id);
|
||||
const receipt = api.reportHostOperationStatus(api.host.id, api.operation.id, { state: 'completed', result });
|
||||
expect((await first).result).toEqual(result);
|
||||
expect((await second).result).toEqual(result);
|
||||
expect(receipt.result).toMatchObject({ retained: false, size: 17 });
|
||||
expect(api.getHostOperation(api.operation.id)?.result).toEqual(receipt.result);
|
||||
const stored = api.db().prepare('SELECT result FROM ec_host_operations WHERE id=?').get(api.operation.id);
|
||||
expect(JSON.stringify(stored)).not.toMatch(/private log body|contentBase64|cursor|"lines"/);
|
||||
expect(api.db().prepare("SELECT * FROM file_blob_references WHERE owner_type='operation-result' AND owner_id=?").all(api.operation.id)).toEqual([]);
|
||||
// Completed reads cannot serve a stale body, including duplicate receipts.
|
||||
await expect(api.waitHostOperationStatus(api.operation.id)).rejects.toThrow('no longer available');
|
||||
api.reportHostOperationStatus(api.host.id, api.operation.id, { state: 'completed', result });
|
||||
await expect(api.waitHostOperationStatus(api.operation.id)).rejects.toThrow('no longer available');
|
||||
});
|
||||
|
||||
it('preserves missing-log results and operation failures', async () => {
|
||||
const api = await setup();
|
||||
const missing = api.waitHostOperationStatus(api.operation.id);
|
||||
api.reportHostOperationStatus(api.host.id, api.operation.id, { state: 'completed', result: null });
|
||||
expect((await missing).result).toBeNull();
|
||||
const failed = api.queueHostOperation(api.host.id, 'logs.read', {});
|
||||
const failure = api.waitHostOperationStatus(failed.id);
|
||||
api.reportHostOperationStatus(api.host.id, failed.id, { state: 'failed', error: 'Read failed' });
|
||||
expect(await failure).toMatchObject({ state: 'failed', error: 'Read failed' });
|
||||
});
|
||||
|
||||
it('discards bodies arriving after the reader has timed out', async () => {
|
||||
const api = await setup();
|
||||
await expect(api.waitHostOperationStatus(api.operation.id, 0)).rejects.toThrow();
|
||||
api.reportHostOperationStatus(api.host.id, api.operation.id, { state: 'completed', result: { lines: ['late body'] } });
|
||||
expect(api.getHostOperation(api.operation.id)?.result).toEqual({ retained: false, lineCount: 1 });
|
||||
await expect(api.waitHostOperationStatus(api.operation.id)).rejects.toThrow('no longer available');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user