import { describe, expect, test } from 'bun:test'; import { convertSecretsToJinja, isAnsibleTemplate } from './ansible-resolver'; import { buildContextFromData } from './context'; // Mock database for tests const mockDb = { $client: { prepare: () => ({ get: () => null, all: () => [], run: () => ({ changes: 0 }), }), }, select: () => ({ from: () => ({ where: () => ({ get: () => null, all: () => [], }), }), }), // biome-ignore lint/suspicious/noExplicitAny: mock database for testing, full type not needed } as any; describe('Ansible Resolver', () => { describe('isAnsibleTemplate', () => { test('should identify .j2 files', async () => { expect(isAnsibleTemplate('config.json.j2')).toBe(true); expect(isAnsibleTemplate('template.j2')).toBe(true); }); test('should identify ansible/ directory files', async () => { expect(isAnsibleTemplate('ansible/playbook.yml')).toBe(true); expect(isAnsibleTemplate('ansible/roles/main.yml')).toBe(true); }); test('should reject non-ansible files', async () => { expect(isAnsibleTemplate('terraform/main.tf')).toBe(false); expect(isAnsibleTemplate('config.json')).toBe(false); }); }); describe('convertSecretsToJinja', () => { test('should convert secret variables to Jinja2 format', async () => { const context = buildContextFromData('test', { secrets: { api_key: 'secret123', db_password: 'pass456', }, }); const template = 'username: $secret:api_key\npassword: $secret:db_password'; const result = await convertSecretsToJinja(template, context, mockDb); expect(result.success).toBe(true); if (result.success) { expect(result.content).toBe('username: {{ api_key }}\npassword: {{ db_password }}'); } }); test('should resolve non-secret variables normally', async () => { const context = buildContextFromData('test', { selfConfig: { hostname: 'myhost', port: '8080', }, systemConfig: { 'dns.primary': '192.168.0.1', }, }); const template = 'host: $self:hostname\nport: $self:port\ndns: $system:dns.primary'; const result = await convertSecretsToJinja(template, context, mockDb); expect(result.success).toBe(true); if (result.success) { expect(result.content).toBe('host: myhost\nport: 8080\ndns: 192.168.0.1'); } }); test('should mix secret conversion and normal resolution', async () => { const context = buildContextFromData('test', { selfConfig: { username: 'admin', }, secrets: { password: 'secret123', }, }); const template = 'user: $self:username\npass: $secret:password'; const result = await convertSecretsToJinja(template, context, mockDb); expect(result.success).toBe(true); if (result.success) { expect(result.content).toBe('user: admin\npass: {{ password }}'); } }); test('should return errors for missing non-secret variables', async () => { const context = buildContextFromData('test', {}); const template = 'host: $self:missing_hostname'; const result = await convertSecretsToJinja(template, context, mockDb); expect(result.success).toBe(false); if (!result.success) { expect(result.errors).toHaveLength(1); expect(result.errors[0]?.variable).toBe('$self:missing_hostname'); } }); test('should convert secrets even if not in context', async () => { const context = buildContextFromData('test', {}); const template = 'pass: $secret:api_key'; const result = await convertSecretsToJinja(template, context, mockDb); // Secrets are always converted to Jinja2, regardless of presence expect(result.success).toBe(true); if (result.success) { expect(result.content).toBe('pass: {{ api_key }}'); } }); test('should handle multiple occurrences of same variable', async () => { const context = buildContextFromData('test', { secrets: { token: 'xyz', }, }); const template = 'auth: $secret:token\nbackup: $secret:token'; const result = await convertSecretsToJinja(template, context, mockDb); expect(result.success).toBe(true); if (result.success) { expect(result.content).toBe('auth: {{ token }}\nbackup: {{ token }}'); } }); }); });