import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { existsSync } from 'node:fs'; import { rm } from 'node:fs/promises'; import { type DbClient, createDbClient } from '../db/client'; import { moduleConfigs, modules } from '../db/schema'; import { resolveAspectTemplate, resolveAspectTemplateRecord } from './aspect-template-resolver'; import { upsertDeployedSystem } from './deployed-systems'; const TEST_DB_PATH = './test-aspect-resolver.db'; /** * Regression coverage for the `$infra:.` selector inside * base_module_aspect ansible_vars. This path has its own hand-rolled * resolver (separate from variables/resolver.ts); it previously handled * only $self:/$system:/$capability:, so knot's migrated * `knot_server_ip: $infra:main.ipv4_address` leaked into resolv.conf * unresolved and broke DNS on every fleet machine. openspec/specs/module-systems-addressing/spec.md. */ function seedModuleWithSystem(db: DbClient, moduleId: string, ip: string): void { db.insert(modules) .values({ id: moduleId, name: moduleId, version: '1.0.0', manifestData: {}, sourcePath: `/tmp/${moduleId}`, }) .run(); db.insert(moduleConfigs) .values({ moduleId, key: 'hostname', value: moduleId, valueJson: JSON.stringify(moduleId) }) .run(); upsertDeployedSystem(db, moduleId, { name: 'main', hostname: `${moduleId}-host`, ipv4Address: ip, zone: 'internal', infraType: 'machine', }); } describe('resolveAspectTemplate — $infra: selector', () => { let db: DbClient; beforeEach(() => { db = createDbClient({ path: TEST_DB_PATH }); seedModuleWithSystem(db, 'knot-unbound-internal', '192.168.0.10'); }); afterEach(async () => { db.$client.close(); for (const suffix of ['', '-shm', '-wal']) { const p = `${TEST_DB_PATH}${suffix}`; if (existsSync(p)) await rm(p); } }); test('resolves $infra:main.ipv4_address to the recorded system IP', async () => { const resolved = await resolveAspectTemplate( '$infra:main.ipv4_address', 'knot-unbound-internal', db, 'base_module_aspect.ansible_vars', ); expect(resolved).toBe('192.168.0.10'); }); test('resolves the braced form and embeds in surrounding text', async () => { const resolved = await resolveAspectTemplate( 'nameserver ${infra:main.ipv4_address}', 'knot-unbound-internal', db, 'base_module_aspect.ansible_vars', ); expect(resolved).toBe('nameserver 192.168.0.10'); }); test('resolveAspectTemplateRecord resolves $infra: across keys', async () => { const resolved = await resolveAspectTemplateRecord( { knot_server_ip: '$infra:main.ipv4_address', knot_host: '$infra:main.hostname' }, 'knot-unbound-internal', db, 'base_module_aspect.ansible_vars', ); expect(resolved).toEqual({ knot_server_ip: '192.168.0.10', knot_host: 'knot-unbound-internal-host', }); }); test('throws on an unknown system name', async () => { expect( resolveAspectTemplate('$infra:replica.ipv4_address', 'knot-unbound-internal', db, 'scope'), ).rejects.toThrow(/Cannot resolve \$infra:replica\.ipv4_address/); }); test('throws on an unknown field', async () => { expect( resolveAspectTemplate('$infra:main.bogus_field', 'knot-unbound-internal', db, 'scope'), ).rejects.toThrow(/Cannot resolve \$infra:main\.bogus_field/); }); });