Maintain scoped operation history totals without table scans
This commit is contained in:
@@ -43,6 +43,40 @@ async function operationHistory() {
|
||||
}
|
||||
|
||||
describe('host operation history', () => {
|
||||
it('maintains exact scoped totals through receipts, expiration, rollback, deletion and reopening without recounting history', async () => {
|
||||
const api = await operationHistory();
|
||||
const { importantOperationSql } = await import('../../src/lib/server/hosts/observations.js');
|
||||
const { closeDb, db } = await import('../../src/lib/server/db/index.js');
|
||||
const ids = Array.from({ length: 35 }, (_, i) => api.add(i % 2 ? 'instance.inspect' : 'instance.restart', { state: 'pending' }));
|
||||
const verify = () => {
|
||||
for (const host of [undefined, api.first.id, api.second.id]) for (const server of [undefined, 'server-one']) {
|
||||
for (const filter of ['all', 'routine', 'important'] as const) {
|
||||
const where = ['o.history_expired_at IS NULL']; const args: string[] = [];
|
||||
if (host) { where.push('o.host_id=?'); args.push(host); }
|
||||
if (server) { where.push('o.server_name=?'); args.push(server); }
|
||||
if (filter !== 'all') where.push(filter === 'routine' ? 'o.routine=1' : importantOperationSql);
|
||||
const expected = db().prepare(`SELECT COUNT(*) total FROM ec_host_operations o WHERE ${where.join(' AND ')}`).get(...args) as { total: number };
|
||||
expect(api.listHostOperationHistory(1, filter, { host, server }).total).toBe(expected.total);
|
||||
}
|
||||
}
|
||||
};
|
||||
verify();
|
||||
for (const [index, id] of ids.entries()) api.reportHostOperationStatus(api.first.id, id,
|
||||
index % 3 ? { state: 'completed', result: { healthy: index % 5 !== 0 } } : { state: 'failed', error: 'failure' });
|
||||
api.handle.prepare("UPDATE ec_host_operations SET server_name='server-one',host_id=? WHERE rowid%2=0").run(api.second.id);
|
||||
api.handle.prepare('UPDATE ec_host_operations SET history_expired_at=? WHERE rowid%4=0').run(new Date().toISOString());
|
||||
api.handle.exec('DELETE FROM ec_host_operation_events WHERE operation_id IN (SELECT id FROM ec_host_operations WHERE rowid%7=0); DELETE FROM ec_host_operations WHERE rowid%7=0');
|
||||
expect(() => api.handle.transaction(() => { api.handle.exec('DELETE FROM ec_host_operation_events; DELETE FROM ec_host_operations'); throw new Error('rollback'); })()).toThrow('rollback');
|
||||
verify(); closeDb(); verify();
|
||||
const prepare = vi.spyOn(db(), 'prepare');
|
||||
api.listHostOperationHistory(1); api.listHostOperationHistory(2);
|
||||
expect(prepare.mock.calls.some(([sql]) => /COUNT\(/i.test(sql))).toBe(false);
|
||||
const count = prepare.mock.calls.find(([sql]) => sql.includes('FROM ec_host_operation_history_counts'))![0];
|
||||
expect(db().prepare('EXPLAIN QUERY PLAN ' + count).all('', '')).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ detail: expect.stringContaining('USING PRIMARY KEY') })
|
||||
]));
|
||||
});
|
||||
|
||||
it('scopes counts and pages to one host or server and preserves deleted-host history', async () => {
|
||||
const api = await seedHistory(90);
|
||||
api.handle.prepare("UPDATE ec_host_operations SET server_name='server-one' WHERE host_id=?").run(api.first.id);
|
||||
@@ -223,6 +257,11 @@ describe('host operation history', () => {
|
||||
const manual = api.add('rcon.execute', { payload: { command: 'say command-secret' } });
|
||||
const unsafe = api.add('files.read', { payload: { path: '../private-secret' } });
|
||||
api.handle.exec(`DROP INDEX ec_host_operations_routine_history;
|
||||
DROP TRIGGER operation_history_count_insert;
|
||||
DROP TRIGGER operation_history_count_delete;
|
||||
DROP TRIGGER operation_history_count_update;
|
||||
DROP TABLE ec_host_operation_history_counts;
|
||||
DELETE FROM ec_host_schema_migrations WHERE name='history-counts-v1';
|
||||
DROP INDEX ec_host_operations_important_history;
|
||||
DROP INDEX ec_host_operations_server_history;
|
||||
ALTER TABLE ec_host_operations DROP COLUMN routine;
|
||||
|
||||
Reference in New Issue
Block a user