Keep native collection launches responsive
This commit is contained in:
@@ -15,6 +15,68 @@ async function enrolled() {
|
||||
}
|
||||
|
||||
describe('native host enrollment and scoped authentication', () => {
|
||||
it('queues serialized launch files and processes receipts without hydrating plugin payloads', async () => {
|
||||
const api = await enrolled();
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const payload = { files: [{ path: 'plugins/fixture.dll', contentBase64: Buffer.alloc(8 * 1024 ** 2, 42).toString('base64') }] };
|
||||
const bytes = Buffer.from(JSON.stringify(payload));
|
||||
const options = { instanceId: 'immutable-instance', generation: 1, idempotencyKey: 'large-launch' };
|
||||
const parse = JSON.parse;
|
||||
const parsing = vi.spyOn(JSON, 'parse').mockImplementation((value, reviver) => {
|
||||
if (value.length > 1024 * 1024) throw new Error('Launch payload was unnecessarily parsed.');
|
||||
return parse(value, reviver);
|
||||
});
|
||||
let id: string;
|
||||
try {
|
||||
const operation = api.queueHostOperationSerialized(api.host.id, 'instance.create', bytes, options);
|
||||
id = operation.id;
|
||||
expect(operation).not.toHaveProperty('payload');
|
||||
expect(api.queueHostOperationSerialized(api.host.id, 'instance.create', bytes, options).id).toBe(id);
|
||||
expect(() => api.queueHostOperationSerialized(api.host.id, 'instance.create', Buffer.from('{}'), options)).toThrow('different request');
|
||||
const stored = db().prepare('SELECT typeof(payload) AS kind,count(*) AS count FROM ec_host_operations WHERE idempotency_key=?').get(options.idempotencyKey);
|
||||
expect(stored).toEqual({ kind: 'text', count: 1 });
|
||||
const waiting = api.waitHostOperationStatus(id);
|
||||
expect(api.reportHostOperationStatus(api.host.id, id, { state: 'accepted' }).state).toBe('accepted');
|
||||
expect(api.reportHostOperationStatus(api.host.id, id, { state: 'running' }).state).toBe('running');
|
||||
api.reportHostOperationStatus(api.host.id, id, { state: 'completed', result: { healthy: true } });
|
||||
expect(await waiting).toMatchObject({ id, state: 'completed', result: { healthy: true } });
|
||||
expect(api.reportHostOperationStatus(api.host.id, id, { state: 'running' }).state).toBe('completed');
|
||||
expect(api.getHostOperationStatus(id)).not.toHaveProperty('payload');
|
||||
const other = api.createHost({ name: 'Other host' });
|
||||
expect(() => api.reportHostOperationStatus(other.id, id, { state: 'completed' })).toThrow('this host');
|
||||
} finally { parsing.mockRestore(); }
|
||||
// Full-data APIs retain their original contract for explicit readers.
|
||||
expect(api.getHostOperation(id!)?.payload).toEqual(payload);
|
||||
expect(api.reportHostOperation(api.host.id, id!, { state: 'running' }).payload).toEqual(payload);
|
||||
expect((await api.waitHostOperation(id!)).payload).toEqual(payload);
|
||||
});
|
||||
|
||||
it('keeps serialized launch routing rows small while full-data APIs hydrate separate immutable bytes', async () => {
|
||||
const api = await enrolled();
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
const routing = { libraryId: 'test-library', name: 't5_zombie_theater_1', game: 't5' };
|
||||
const payload = { ...routing, files: [{ contentBase64: 'A'.repeat(512 * 1024) }] };
|
||||
const bytes = Buffer.from(JSON.stringify(payload));
|
||||
const launch = api.queueHostOperationSerialized(api.host.id, 'instance.create', bytes, {}, routing);
|
||||
const inline = api.queueHostOperation(api.host.id, 'instance.inspect', { name: 'inline-legacy' });
|
||||
expect(api.getHostOperation(launch.id)?.payload).toEqual(payload);
|
||||
expect(api.listHostOperations(api.host.id).find(operation => operation.id === launch.id)?.payload).toEqual(payload);
|
||||
expect(api.pendingHostOperations(api.host.id, 'write').map(operation => operation.payload)).toEqual([payload]);
|
||||
expect(api.pendingHostOperations(api.host.id, 'read')).toEqual([inline]);
|
||||
expect(api.pendingHostOperationRows(api.host.id, 'write', false)[0]).not.toHaveProperty('payload');
|
||||
api.reportHostOperationStatus(api.host.id, launch.id, { state: 'accepted' });
|
||||
api.reportHostOperationStatus(api.host.id, launch.id, { state: 'running', result: { phase: 'starting' } });
|
||||
const stored = db().prepare(`SELECT payload,last_delivered_at FROM ec_host_operations WHERE id=?`).get(launch.id) as
|
||||
{ payload: string; last_delivered_at: string | null };
|
||||
expect(stored.payload).toBe(JSON.stringify(routing));
|
||||
expect(stored.last_delivered_at).not.toBeNull();
|
||||
expect(db().prepare('SELECT length(CAST(payload AS BLOB)) AS bytes FROM ec_host_operation_payloads WHERE operation_id=?').get(launch.id))
|
||||
.toEqual({ bytes: bytes.length });
|
||||
expect(api.getHostOperationPayload(api.host.id, launch.id)).toEqual(bytes);
|
||||
expect(api.getHostOperationPayload(api.host.id, inline.id).toString('utf8')).toBe(JSON.stringify(inline.payload));
|
||||
expect(db().prepare('SELECT 1 FROM ec_host_operation_payloads WHERE operation_id=?').get(inline.id)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('narrows cancellation lookups by kind instead of scanning completed health history', async () => {
|
||||
const api = await enrolled();
|
||||
api.heartbeat(api.host.id, { ...inventory, capabilities: ['instance.cancel-create', 'operations.health-lane'] });
|
||||
@@ -29,7 +91,7 @@ describe('native host enrollment and scoped authentication', () => {
|
||||
const inspect = api.queueHostOperation(api.host.id, 'instance.inspect', {});
|
||||
const prepare = vi.spyOn(handle, 'prepare');
|
||||
expect(api.pendingHostOperations(api.host.id, 'health').map(op => op.id)).toEqual([inspect.id]);
|
||||
const query = prepare.mock.calls.map(([sql]) => sql).find(sql => sql.includes('SELECT * FROM ec_host_operations o'))!;
|
||||
const query = prepare.mock.calls.map(([sql]) => sql).find(sql => sql.includes('FROM ec_host_operations o WHERE host_id=?'))!;
|
||||
prepare.mockRestore();
|
||||
// Assert the actual poll's access path, avoiding hardware-dependent timing thresholds.
|
||||
const plan = handle.prepare(`EXPLAIN QUERY PLAN ${query}`).all(api.host.id, 1, 1) as { detail: string }[];
|
||||
|
||||
Reference in New Issue
Block a user