Add host capacity sample viewer with filtering and pagination
This commit is contained in:
@@ -38,6 +38,57 @@ async function learned() {
|
||||
}
|
||||
|
||||
describe('measured instance capacity', () => {
|
||||
it('lists retained samples with phase filters, host scope, stable ordering, and bounded pages', async () => {
|
||||
for (let minute = 0; minute < 101; minute++) await report(minute, { uptimeSeconds: minute < 5 ? minute * 60 : 600 });
|
||||
const { capacitySamples } = await import('../../src/lib/server/hosts/calibration.js');
|
||||
const first = capacitySamples(hostId, { scope: 'host', phase: 'all', page: 1 });
|
||||
const second = capacitySamples(hostId, { scope: 'host', phase: 'all', page: 2 });
|
||||
expect(first).toMatchObject({ total: 101, page: 1, pageSize: 50 });
|
||||
expect(first.samples).toHaveLength(50);
|
||||
expect(first.samples[0]).toMatchObject({ hostId, hostName: 'First', instanceId: sample.instanceId,
|
||||
server: 't5_zombie_theater_instance-1', generation: 1, phase: 'running',
|
||||
cpuCores: 0.1, memoryBytes: GiB / 4, timestamp: new Date(epoch + 100 * 60_000).toISOString() });
|
||||
expect(new Set([...first.samples, ...second.samples].map(row => row.timestamp)).size).toBe(100);
|
||||
expect(capacitySamples(hostId, { scope: 'host', phase: 'all', page: 999 })).toMatchObject({ page: 3, samples: [expect.any(Object)] });
|
||||
const startup = capacitySamples(hostId, { scope: 'host', phase: 'startup', page: 1 });
|
||||
expect(startup.total).toBe(5);
|
||||
expect(startup.samples.every(row => row.phase === 'startup')).toBe(true);
|
||||
expect(capacitySamples(hostId, { scope: 'host', phase: 'running', page: 1 }).total).toBe(96);
|
||||
vi.setSystemTime(epoch + 26 * 60 * 60_000);
|
||||
expect(capacitySamples(hostId, { scope: 'host', phase: 'all', page: 1 })).toMatchObject({ total: 0, samples: [] });
|
||||
});
|
||||
|
||||
it('supports fleet samples and retains instance identity after retirement', async () => {
|
||||
await report(0);
|
||||
const hosts = await import('../../src/lib/server/hosts/index.js');
|
||||
const other = hosts.createHost({ name: 'Second' });
|
||||
hosts.enrollHost(hosts.createEnrollment(other.id).token, inventory);
|
||||
await instance(other.id, 'instance-2');
|
||||
await report(1, { instanceId: 'instance-2' }, other.id);
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
db().prepare('UPDATE native_instances SET retired_at=? WHERE id=?').run(new Date().toISOString(), sample.instanceId);
|
||||
const { capacitySamples } = await import('../../src/lib/server/hosts/calibration.js');
|
||||
expect(capacitySamples(hostId, { scope: 'host', phase: 'all', page: 1 }).samples.map(row => row.instanceId)).toEqual([sample.instanceId]);
|
||||
const fleet = capacitySamples(hostId, { scope: 'fleet', phase: 'all', page: 1 });
|
||||
expect(fleet.total).toBe(2);
|
||||
expect(fleet.samples.map(row => row.hostName)).toEqual(['Second', 'First']);
|
||||
});
|
||||
|
||||
it('validates the samples API and rejects agent credentials and cross-origin reads', async () => {
|
||||
await report(0);
|
||||
const { GET } = await import('../../src/routes/api/hosts/[id]/samples/+server.js');
|
||||
const request = (query = '', headers = {}, id = hostId) => GET({ request: new Request(`https://ec.test/api/hosts/${id}/samples${query}`, { headers }), params: { id } });
|
||||
const response = request();
|
||||
expect(response.headers.get('cache-control')).toBe('no-store');
|
||||
expect(await response.json()).toMatchObject({ total: 1, samples: [expect.objectContaining({ cpuCores: 0.1 })] });
|
||||
for (const query of ['?scope=wrong', '?phase=wrong', '?page=-1', '?page=1.5', '?page=NaN', '?page=Infinity']) {
|
||||
expect(() => request(query)).toThrow(expect.objectContaining({ status: 400 }));
|
||||
}
|
||||
expect(() => request('', {}, 'missing')).toThrow(expect.objectContaining({ status: 404 }));
|
||||
expect(() => request('', { origin: 'https://other.test' })).toThrow(expect.objectContaining({ status: 403 }));
|
||||
expect(() => request('', { authorization: 'Bearer ec_host_test' })).toThrow(expect.objectContaining({ status: 403 }));
|
||||
});
|
||||
|
||||
it('reports sampling failure without inventing measurements and clears it after recovery', async () => {
|
||||
const { heartbeat, getHost } = await import('../../src/lib/server/hosts/index.js');
|
||||
const { capacityCalibration } = await import('../../src/lib/server/hosts/calibration.js');
|
||||
|
||||
Reference in New Issue
Block a user