import Database from 'better-sqlite3'; import { describe, expect, it } from 'vitest'; import { migrateDatabase } from '../../infrastructure/database/migrations.js'; import { resolveOperationTargets, resolveTargets } from './target-resolver.js'; function fixture() { const db = new Database(':memory:'); db.pragma('foreign_keys = ON'); migrateDatabase(db); const now = '2026-07-30T00:00:00.000Z'; const insert = db.prepare( 'INSERT INTO instances (id,name,base_url,enabled,config_revision,created_at,updated_at) VALUES (?,?,?,?,?,?,?)', ); insert.run('a', 'Alpha', 'http://10.0.0.1', 1, 3, now, now); insert.run('b', 'Beta', 'http://10.0.0.2', 1, 2, now, now); insert.run('c', 'Disabled', 'http://10.0.0.3', 0, 1, now, now); const tag = db.prepare('INSERT INTO instance_tags (instance_id,tag,created_at) VALUES (?,?,?)'); tag.run('a', 'lab', now); tag.run('a', 'east', now); tag.run('b', 'lab', now); tag.run('c', 'east', now); return db; } describe('resolveTargets', () => { it('resolves enabled fixed targets in selector order with current revisions', () => { const db = fixture(); expect(resolveTargets(db, { mode: 'fixed', instanceIds: ['b', 'c', 'a'] })).toEqual([ { id: 'b', revision: 2 }, { id: 'a', revision: 3 }, ]); db.close(); }); it('resolves dynamic all and any tag matches on every call', () => { const db = fixture(); expect(resolveTargets(db, { mode: 'tags', match: 'all', tags: ['lab', 'east'] })).toEqual([ { id: 'a', revision: 3 }, ]); expect(resolveTargets(db, { mode: 'tags', match: 'any', tags: ['lab', 'east'] })).toEqual([ { id: 'a', revision: 3 }, { id: 'b', revision: 2 }, ]); db.prepare( 'INSERT INTO instances (id,name,base_url,enabled,config_revision,created_at,updated_at) VALUES (?,?,?,?,?,?,?)', ).run( 'd', 'Dynamic', 'http://10.0.0.4', 1, 1, '2026-07-30T00:00:00.000Z', '2026-07-30T00:00:00.000Z', ); db.prepare('INSERT INTO instance_tags (instance_id,tag,created_at) VALUES (?,?,?)').run( 'd', 'east', '2026-07-30T00:00:00.000Z', ); expect( resolveTargets(db, { mode: 'tags', match: 'any', tags: ['east'] }).map((x) => x.id), ).toEqual(['a', 'd']); db.close(); }); it('resolves the Hub all-devices and group selectors', () => { const db = fixture(); const now = '2026-07-30T00:00:00.000Z'; db.prepare( 'INSERT INTO device_groups (id,name,description,created_at,updated_at) VALUES (?,?,?,?,?)', ).run('g-lab', '实验室', '', now, now); db.prepare('UPDATE instances SET group_id = ? WHERE id IN (?,?)').run('g-lab', 'a', 'c'); expect(resolveTargets(db, { mode: 'all' })).toEqual([ { id: 'a', revision: 3 }, { id: 'b', revision: 2 }, ]); expect(resolveTargets(db, { mode: 'group', groupId: 'g-lab' })).toEqual([ { id: 'a', revision: 3 }, ]); expect(resolveTargets(db, { mode: 'groups', groupIds: ['g-lab', 'g-office'] })).toEqual([ { id: 'a', revision: 3 }, ]); db.close(); }); it('dispatches control-plane backups without resolving device targets', () => { const db = fixture(); expect(resolveOperationTargets(db, { mode: 'all' }, 'backup-data')).toEqual({ targets: [{ id: 'control-plane', revision: 1 }], targetSnapshot: [], unavailableInstanceIds: [], }); db.close(); }); it('dispatches devices whose capability was never probed', () => { const db = fixture(); expect(resolveOperationTargets(db, { mode: 'all' }, 'restart-baseband')).toEqual({ targets: [ { id: 'a', revision: 3 }, { id: 'b', revision: 2 }, ], targetSnapshot: ['a', 'b'], unavailableInstanceIds: [], }); db.close(); }); it('separates targets whose required operation capability is unavailable', () => { const db = fixture(); const now = '2026-07-30T00:00:00.000Z'; db.prepare( 'INSERT INTO capabilities (instance_id,operation_id,state,observed_at,created_at,updated_at) VALUES (?,?,?,?,?,?)', ).run('a', 'postServiceRestart', 'supported', now, now, now); db.prepare( 'INSERT INTO capabilities (instance_id,operation_id,state,observed_at,created_at,updated_at) VALUES (?,?,?,?,?,?)', ).run('b', 'postServiceRestart', 'unsupported', now, now, now); expect( resolveOperationTargets(db, { mode: 'fixed', instanceIds: ['a', 'b'] }, 'restart-service'), ).toEqual({ targets: [{ id: 'a', revision: 3 }], targetSnapshot: ['a', 'b'], unavailableInstanceIds: ['b'], }); db.close(); }); });