import Database from 'better-sqlite3'; import { createHash } from 'node:crypto'; import { afterEach, beforeEach, expect, it, vi } from 'vitest'; import schema from '../../src/lib/server/db/schema.sql?raw'; import { newFixture, type Fixture } from '../helpers/fixture.js'; let fixture: Fixture; beforeEach(async () => { vi.resetModules(); fixture = await newFixture([]); }); afterEach(async () => { vi.restoreAllMocks(); await fixture.cleanup(); }); it('shares bytes across versions, staged copies and commands, and scopes downloads to the assigned host', async () => { const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { createScript } = await import('../../src/lib/server/fleet/scripts.js'); const { queueFileChange } = await import('../../src/lib/server/fleet/staged-files.js'); const hosts = await import('../../src/lib/server/hosts/index.js'); const { pruneFileBlobs } = await import('../../src/lib/server/db/file-blobs.js'); const content = Buffer.alloc(1024 * 1024, 71); const hash = createHash('sha256').update(content).digest('hex'); for (let i = 0; i < 20; i++) { createScript({ name: `plugin-${i}`, kind: 'plugin', targetRelPath: 'plugin.dll', content }); queueFileChange({ sourceKind: 'campaign', sourceId: 'campaign', server: `t5_server_${i}`, kind: 'plugin', relPath: 'plugin.dll', content, inventorySource: 'test' }); } const host = hosts.createHost({ name: 'Owner' }); const credential = hosts.enrollHost(hosts.createEnrollment(host.id).token, { capabilities: ['files.blobs'] }).credential; const other = hosts.createHost({ name: 'Other' }); const otherCredential = hosts.enrollHost(hosts.createEnrollment(other.id).token, {}).credential; const payload = { stageId: 'one', files: [ { path: 'plugins/plugin.dll', contentBase64: content.toString('base64'), sha256: hash } ] }; const op = hosts.queueHostOperation(host.id, 'files.stage', payload, { idempotencyKey: 'one' }); expect( hosts.queueHostOperation(host.id, 'files.stage', payload, { idempotencyKey: 'one' }).id ).toBe(op.id); expect(() => hosts.queueHostOperation( host.id, 'files.stage', { ...payload, stageId: 'two' }, { idempotencyKey: 'one' } ) ).toThrow('different request'); expect(db().prepare('SELECT COUNT(*) n,SUM(size) bytes FROM file_blobs').get()).toEqual({ n: 1, bytes: content.length }); const packed = JSON.parse(hosts.getHostOperationPayload(host.id, op.id).toString()); expect(packed.files[0]).toEqual({ path: 'plugins/plugin.dll', contentSha256: hash, sha256: hash }); expect(pruneFileBlobs()).toBe(0); const { GET } = await import('../../src/routes/api/agent/blobs/[sha256]/+server.js'); const request = (token: string) => new Request(`https://ec.test/api/agent/blobs/${hash}`, { headers: { authorization: `Bearer ${token}` } }); expect( Buffer.from( await GET({ request: request(credential), params: { sha256: hash } }).arrayBuffer() ).equals(content) ).toBe(true); expect(() => GET({ request: request(otherCredential), params: { sha256: hash } })).toThrow(); closeDb(); expect(hosts.getHostOperation(op.id)?.payload).toEqual(payload); // Previous releases receive their original inline protocol during a rolling upgrade. hosts.heartbeat(host.id, {}); expect(JSON.parse(hosts.getHostOperationPayload(host.id, op.id).toString())).toEqual(payload); }); it('backfills legacy bytes once while preserving foreign keys, IDs and pending work', async () => { const legacy = new Database(fixture.dbPath); const content = Buffer.from([0, 255, 1, 88]); const hash = createHash('sha256').update(content).digest('hex'); try { legacy.exec( schema.replace( /\b(content|content_before|content_after|prior_content)_hash(\s+)TEXT REFERENCES file_blobs\(sha256\)/g, '$1$2BLOB' ) ); legacy.exec("INSERT INTO scripts(id,name,target_rel_path) VALUES(7,'legacy','plugin.dll')"); legacy .prepare( 'INSERT INTO script_versions(id,script_id,version,sha256,size,content) VALUES(9,7,1,?,?,?)' ) .run(hash, content.length, content); legacy .prepare( "INSERT INTO staged_file_changes(source_kind,source_id,server,kind,rel_path,content,inventory_source) VALUES('campaign','old','t5_one','plugin','plugin.dll',?,'test')" ) .run(content); legacy.exec( "INSERT INTO deployments(id,action,started_at) VALUES('old','promote','2026-10-01T00:00:00.000Z')" ); legacy .prepare( "INSERT INTO deployment_files(deployment_id,server,verb,rel_path,content_before,content_after) VALUES('old','t5_one','updated','plugin.dll',?,?)" ) .run(content, content); } finally { legacy.close(); } const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const { getVersionContent } = await import('../../src/lib/server/fleet/scripts.js'); for (let pass = 0; pass < 2; pass++) { expect(getVersionContent(9)).toEqual(content); expect(db().prepare('SELECT content_hash,state FROM staged_file_changes').get()).toEqual({ content_hash: hash, state: 'pending' }); expect(db().prepare('SELECT COUNT(*) n FROM file_blobs').get()).toEqual({ n: 1 }); expect(db().pragma('foreign_key_check')).toEqual([]); expect( (db().pragma('table_info(script_versions)') as { name: string }[]).some( (column) => column.name === 'content' ) ).toBe(false); closeDb(); } }); it('migrates legacy command and receipt bytes without changing replay identity', async () => { const { db, closeDb } = await import('../../src/lib/server/db/index.js'); const hosts = await import('../../src/lib/server/hosts/index.js'); const host = hosts.createHost({ name: 'Legacy' }); hosts.enrollHost(hosts.createEnrollment(host.id).token, {}); const payload = { stageId: 'legacy', files: [{ path: 'plugin.dll', contentBase64: 'AAEC' }] }; const result = { before: [{ path: 'plugin.dll', contentBase64: 'AAEC' }] }; db() .prepare( `INSERT INTO ec_host_operations(id,host_id,kind,idempotency_key,payload,state,result,created_at,updated_at) VALUES('legacy',?,'files.stage','legacy',?,'running',?, ?,?)` ) .run( host.id, JSON.stringify(payload), JSON.stringify(result), new Date().toISOString(), new Date().toISOString() ); db() .prepare( "DELETE FROM ec_host_schema_migrations WHERE name IN ('staged-operation-payloads','operation-file-blobs')" ) .run(); db().pragma('user_version = 0'); closeDb(); expect(hosts.getHostOperation('legacy')).toMatchObject({ payload, result, state: 'running' }); expect( hosts.queueHostOperation(host.id, 'files.stage', payload, { idempotencyKey: 'legacy' }).id ).toBe('legacy'); expect(db().prepare('SELECT COUNT(*) n FROM file_blobs').get()).toEqual({ n: 1 }); expect(hosts.getHostOperationStatus('legacy')?.result).toEqual({ before: [ { path: 'plugin.dll', contentSha256: createHash('sha256') .update(Buffer.from('AAEC', 'base64')) .digest('hex') } ] }); });