101 lines
4.0 KiB
TypeScript
101 lines
4.0 KiB
TypeScript
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'
|
|
);
|
|
});
|
|
});
|