Files
Codewalkers/apps/server/preview/compose-generator.test.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt
standard monorepo conventions (apps/ for runnable apps, packages/
for reusable libraries). Update all config files, shared package
imports, test fixtures, and documentation to reflect new paths.

Key fixes:
- Update workspace config to ["apps/*", "packages/*"]
- Update tsconfig.json rootDir/include for apps/server/
- Add apps/web/** to vitest exclude list
- Update drizzle.config.ts schema path
- Fix ensure-schema.ts migration path detection (3 levels up in dev,
  2 levels up in dist)
- Fix tests/integration/cli-server.test.ts import paths
- Update packages/shared imports to apps/server/ paths
- Update all docs/ files with new paths
2026-03-03 11:22:53 +01:00

208 lines
6.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import yaml from 'js-yaml';
import {
generateComposeFile,
generateCaddyfile,
generateLabels,
} from './compose-generator.js';
import type { PreviewConfig } from './types.js';
describe('generateComposeFile', () => {
const baseOpts = {
projectPath: '/workspace/repos/my-project-abc123',
port: 9100,
deploymentId: 'test123',
labels: {
'cw.preview': 'true',
'cw.initiative-id': 'init-1',
'cw.port': '9100',
},
};
it('generates valid compose YAML with user services and Caddy proxy', () => {
const config: PreviewConfig = {
version: 1,
services: {
app: {
name: 'app',
build: '.',
port: 3000,
},
},
};
const result = generateComposeFile(config, baseOpts);
const parsed = yaml.load(result) as Record<string, any>;
// Has both user service and caddy
expect(parsed.services.app).toBeDefined();
expect(parsed.services['caddy-proxy']).toBeDefined();
// Network present
expect(parsed.networks.preview).toBeDefined();
// Caddy publishes port
expect(parsed.services['caddy-proxy'].ports).toContain('9100:80');
// Labels propagated
expect(parsed.services.app.labels['cw.preview']).toBe('true');
});
it('handles object build config with context path joining', () => {
const config: PreviewConfig = {
version: 1,
services: {
api: {
name: 'api',
build: { context: 'packages/api', dockerfile: 'Dockerfile.prod' },
port: 8080,
},
},
};
const result = generateComposeFile(config, baseOpts);
const parsed = yaml.load(result) as Record<string, any>;
expect(parsed.services.api.build.context).toBe(
'/workspace/repos/my-project-abc123/packages/api',
);
expect(parsed.services.api.build.dockerfile).toBe('Dockerfile.prod');
});
it('handles image-based services', () => {
const config: PreviewConfig = {
version: 1,
services: {
db: {
name: 'db',
image: 'postgres:16',
port: 5432,
internal: true,
env: { POSTGRES_PASSWORD: 'test' },
},
},
};
const result = generateComposeFile(config, baseOpts);
const parsed = yaml.load(result) as Record<string, any>;
expect(parsed.services.db.image).toBe('postgres:16');
expect(parsed.services.db.environment.POSTGRES_PASSWORD).toBe('test');
});
it('caddy depends on all user services', () => {
const config: PreviewConfig = {
version: 1,
services: {
frontend: { name: 'frontend', build: '.', port: 3000 },
backend: { name: 'backend', build: '.', port: 8080 },
},
};
const result = generateComposeFile(config, baseOpts);
const parsed = yaml.load(result) as Record<string, any>;
expect(parsed.services['caddy-proxy'].depends_on).toContain('frontend');
expect(parsed.services['caddy-proxy'].depends_on).toContain('backend');
});
});
describe('generateCaddyfile', () => {
it('generates simple single-service Caddyfile', () => {
const config: PreviewConfig = {
version: 1,
services: {
app: { name: 'app', build: '.', port: 3000 },
},
};
const caddyfile = generateCaddyfile(config);
expect(caddyfile).toContain(':80 {');
expect(caddyfile).toContain('reverse_proxy app:3000');
expect(caddyfile).toContain('}');
});
it('generates multi-service Caddyfile with handle_path for non-root routes', () => {
const config: PreviewConfig = {
version: 1,
services: {
frontend: { name: 'frontend', build: '.', port: 3000, route: '/' },
backend: { name: 'backend', build: '.', port: 8080, route: '/api' },
},
};
const caddyfile = generateCaddyfile(config);
expect(caddyfile).toContain('handle_path /api/*');
expect(caddyfile).toContain('reverse_proxy backend:8080');
expect(caddyfile).toContain('handle {');
expect(caddyfile).toContain('reverse_proxy frontend:3000');
});
it('excludes internal services from Caddyfile', () => {
const config: PreviewConfig = {
version: 1,
services: {
app: { name: 'app', build: '.', port: 3000 },
db: { name: 'db', image: 'postgres', port: 5432, internal: true },
},
};
const caddyfile = generateCaddyfile(config);
expect(caddyfile).not.toContain('postgres');
expect(caddyfile).not.toContain('db:5432');
});
it('sorts routes by specificity (longer paths first)', () => {
const config: PreviewConfig = {
version: 1,
services: {
app: { name: 'app', build: '.', port: 3000, route: '/' },
api: { name: 'api', build: '.', port: 8080, route: '/api' },
auth: { name: 'auth', build: '.', port: 9090, route: '/api/auth' },
},
};
const caddyfile = generateCaddyfile(config);
const apiAuthIdx = caddyfile.indexOf('/api/auth');
const apiIdx = caddyfile.indexOf('handle_path /api/*');
const handleIdx = caddyfile.indexOf('handle {');
// /api/auth should come before /api which should come before /
expect(apiAuthIdx).toBeLessThan(apiIdx);
expect(apiIdx).toBeLessThan(handleIdx);
});
});
describe('generateLabels', () => {
it('generates correct labels', () => {
const labels = generateLabels({
initiativeId: 'init-1',
phaseId: 'phase-1',
projectId: 'proj-1',
branch: 'feature/test',
port: 9100,
previewId: 'abc123',
});
expect(labels['cw.preview']).toBe('true');
expect(labels['cw.initiative-id']).toBe('init-1');
expect(labels['cw.phase-id']).toBe('phase-1');
expect(labels['cw.project-id']).toBe('proj-1');
expect(labels['cw.branch']).toBe('feature/test');
expect(labels['cw.port']).toBe('9100');
expect(labels['cw.preview-id']).toBe('abc123');
});
it('omits phaseId label when not provided', () => {
const labels = generateLabels({
initiativeId: 'init-1',
projectId: 'proj-1',
branch: 'main',
port: 9100,
previewId: 'abc123',
});
expect(labels['cw.phase-id']).toBeUndefined();
});
});