import { describe, test, expect } from 'bun:test' import { Buffer } from 'node:buffer' import { generateKeyPairSync, randomUUID } from 'node:crypto' import { chmodSync, mkdtempSync, writeFileSync, mkdirSync } from 'node:fs' import { join } from 'node:path' import { tmpdir } from 'node:os' // Generate a test RSA keypair const { publicKey: testPublicPem, privateKey: testPrivatePem } = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, }) const testKid = 'test-key-id-123' describe('localhost-utils', () => { describe('parseApiGatewayKeys', () => { test('parses multiline RSA keys from .env file', async () => { const { parseApiGatewayKeys } = await import('./localhost-utils.ts') // Create a temp directory mimicking the repo structure const tmpDir = mkdtempSync(join(tmpdir(), 'tela-test-')) const envDir = join(tmpDir, '.repositories', 'api-gateway') mkdirSync(envDir, { recursive: true }) const envContent = `CURRENT_KEY_ID='${testKid}'\nCURRENT_KEY_PRIVATE='${testPrivatePem}'\nCURRENT_KEY_PUBLIC='${testPublicPem}'` writeFileSync(join(envDir, '.env'), envContent) const keys = parseApiGatewayKeys(tmpDir) expect(keys.kid).toBe(testKid) expect(keys.privateKey).toContain('BEGIN PRIVATE KEY') expect(keys.publicKey).toContain('BEGIN PUBLIC KEY') }) test('throws when .env file is missing', async () => { const { parseApiGatewayKeys } = await import('./localhost-utils.ts') expect(() => parseApiGatewayKeys('/nonexistent/path')).toThrow('api-gateway .env not found') }) }) describe('signLocalhostJwt', () => { test('produces a valid 3-part JWT', async () => { const { signLocalhostJwt } = await import('./localhost-utils.ts') const token = signLocalhostJwt({ workspace: { id: randomUUID(), title: 'Test Workspace' }, privateKey: testPrivatePem, kid: testKid, }) const parts = token.split('.') expect(parts).toHaveLength(3) // Decode and verify header const header = JSON.parse(Buffer.from(parts[0]!, 'base64url').toString()) expect(header.alg).toBe('RS256') expect(header.typ).toBe('JWT') expect(header.kid).toBe(testKid) // Decode and verify payload const payload = JSON.parse(Buffer.from(parts[1]!, 'base64url').toString()) expect(payload.iss).toBe('api.tela.com') expect(payload.workspace.title).toBe('Test Workspace') expect(payload.requestId).toBeDefined() expect(payload.iat).toBeDefined() expect(payload.exp).toBeGreaterThan(payload.iat) }) }) describe('getDockerPort', () => { test('returns null for non-existent container', async () => { const { getDockerPort } = await import('./localhost-utils.ts') const port = getDockerPort('nonexistent-container-xyz') expect(port).toBeNull() }) }) describe('resolveDockerContainerName', () => { test('resolves a compose service name to the real container name', async () => { const { resolveDockerContainerName } = await import('./localhost-utils.ts') // Arrange const dockerPsOutput = [ 'auth-api-auth-api-1\tauth-api', 'tela-api\ttela-api', 'auth-api-auth-postgres-1\tauth-postgres', ].join('\n') // Act const containerName = resolveDockerContainerName('auth-api', dockerPsOutput) // Assert expect(containerName).toBe('auth-api-auth-api-1') }) test('prefers the compose-style suffix match when labels are missing', async () => { const { resolveDockerContainerName } = await import('./localhost-utils.ts') // Arrange const dockerPsOutput = [ 'auth-api-auth-postgres-1\t', 'auth-api-auth-api-1\t', ].join('\n') // Act const containerName = resolveDockerContainerName('auth-api', dockerPsOutput) // Assert expect(containerName).toBe('auth-api-auth-api-1') }) }) describe('generateLocalhostJwt', () => { test('wraps bun failures with stderr details', async () => { const { generateLocalhostJwt } = await import('./localhost-utils.ts') // Arrange const tmpDir = mkdtempSync(join(tmpdir(), 'tela-test-')) const authApiDir = join(tmpDir, '.repositories', 'auth-api') mkdirSync(authApiDir, { recursive: true }) writeFileSync( join(authApiDir, '.env'), [ 'BETTER_AUTH_SECRET=test-secret', 'BETTER_AUTH_URL=http://localhost:3000', 'DATABASE_URL=postgresql://postgres:postgres@auth-postgres:5432/postgres', ].join('\n'), ) const binDir = join(tmpDir, 'bin') mkdirSync(binDir, { recursive: true }) const fakeBunPath = join(binDir, 'bun') writeFileSync(fakeBunPath, '#!/bin/sh\necho "missing dependency" >&2\nexit 1\n') chmodSync(fakeBunPath, 0o755) const originalPath = process.env.PATH process.env.PATH = `${binDir}:${originalPath ?? ''}` try { // Act + Assert expect(() => generateLocalhostJwt({ reposPath: tmpDir, workspace: { id: randomUUID(), title: 'Test Workspace' }, authApiDbPort: 5433, })).toThrow(/bun subprocess failed:.*missing dependency/s) } finally { if (originalPath === undefined) { delete process.env.PATH } else { process.env.PATH = originalPath } } }) }) describe('readAuthApiSecret', () => { test('reads quoted AUTH_API_SECRET from tela-api .env', async () => { const { readAuthApiSecret } = await import('./localhost-utils.ts') const tmpDir = mkdtempSync(join(tmpdir(), 'tela-test-')) const envDir = join(tmpDir, 'packages', 'api') mkdirSync(envDir, { recursive: true }) writeFileSync(join(envDir, '.env'), "AUTH_API_SECRET='test-secret'\nOTHER=value\n") const secret = readAuthApiSecret(tmpDir) expect(secret).toBe('test-secret') }) test('reads unquoted AUTH_API_SECRET from tela-api .env', async () => { const { readAuthApiSecret } = await import('./localhost-utils.ts') const tmpDir = mkdtempSync(join(tmpdir(), 'tela-test-')) const envDir = join(tmpDir, 'packages', 'api') mkdirSync(envDir, { recursive: true }) writeFileSync(join(envDir, '.env'), 'AUTH_API_SECRET=chefs\nOTHER=value\n') const secret = readAuthApiSecret(tmpDir) expect(secret).toBe('chefs') }) test('throws when .env missing', async () => { const { readAuthApiSecret } = await import('./localhost-utils.ts') expect(() => readAuthApiSecret('/nonexistent')).toThrow('tela-api .env not found') }) }) describe('rewriteDatabaseUrlForHost', () => { test('rewrites the auth-api compose hostname to localhost with the published port', async () => { const { rewriteDatabaseUrlForHost } = await import('./localhost-utils.ts') // Arrange const databaseUrl = 'postgresql://postgres:postgres@auth-postgres:5432/postgres' // Act const rewrittenUrl = rewriteDatabaseUrlForHost(databaseUrl, 5433) // Assert expect(rewrittenUrl).toBe('postgresql://postgres:postgres@localhost:5433/postgres') }) }) })