import { describe, it, expect } from 'vitest'; import { parseCwPreviewConfig } from './config-reader.js'; describe('parseCwPreviewConfig', () => { it('parses minimal single-service config', () => { const raw = ` version: 1 services: app: build: "." port: 3000 `; const config = parseCwPreviewConfig(raw); expect(config.version).toBe(1); expect(Object.keys(config.services)).toEqual(['app']); expect(config.services.app.port).toBe(3000); expect(config.services.app.build).toBe('.'); }); it('parses multi-service config with routes and healthchecks', () => { const raw = ` version: 1 services: frontend: build: context: "." dockerfile: apps/web/Dockerfile port: 3000 route: / healthcheck: path: / interval: 5s retries: 10 env: VITE_API_URL: /api backend: build: context: "." dockerfile: packages/api/Dockerfile port: 8080 route: /api healthcheck: path: /health env: DATABASE_URL: "postgres://db:5432/app" db: image: postgres:16-alpine port: 5432 internal: true env: POSTGRES_PASSWORD: preview `; const config = parseCwPreviewConfig(raw); expect(Object.keys(config.services)).toHaveLength(3); // Frontend expect(config.services.frontend.port).toBe(3000); expect(config.services.frontend.route).toBe('/'); expect(config.services.frontend.healthcheck?.path).toBe('/'); expect(config.services.frontend.healthcheck?.retries).toBe(10); expect(config.services.frontend.env?.VITE_API_URL).toBe('/api'); expect(config.services.frontend.build).toEqual({ context: '.', dockerfile: 'apps/web/Dockerfile', }); // Backend expect(config.services.backend.port).toBe(8080); expect(config.services.backend.route).toBe('/api'); // DB (internal) expect(config.services.db.internal).toBe(true); expect(config.services.db.image).toBe('postgres:16-alpine'); }); it('rejects config without services', () => { expect(() => parseCwPreviewConfig('version: 1\n')).toThrow('missing "services"'); }); it('rejects service without port (unless internal)', () => { const raw = ` version: 1 services: app: build: "." `; expect(() => parseCwPreviewConfig(raw)).toThrow('must specify a "port"'); }); it('allows internal service without port', () => { const raw = ` version: 1 services: redis: image: redis:7 internal: true `; const config = parseCwPreviewConfig(raw); expect(config.services.redis.internal).toBe(true); expect(config.services.redis.port).toBe(0); }); it('normalizes string build to string', () => { const raw = ` version: 1 services: app: build: "./app" port: 3000 `; const config = parseCwPreviewConfig(raw); expect(config.services.app.build).toBe('./app'); }); it('parses dev section with image, command, and workdir', () => { const raw = ` version: 1 services: frontend: build: "." port: 3000 route: / dev: image: node:20-alpine command: npm run dev -- --host 0.0.0.0 workdir: /app `; const config = parseCwPreviewConfig(raw); expect(config.services.frontend.dev).toBeDefined(); expect(config.services.frontend.dev!.image).toBe('node:20-alpine'); expect(config.services.frontend.dev!.command).toBe('npm run dev -- --host 0.0.0.0'); expect(config.services.frontend.dev!.workdir).toBe('/app'); }); it('parses seed array on a service', () => { const raw = ` version: 1 services: app: build: "." port: 3000 seed: - npm run db:migrate - npm run db:seed `; const config = parseCwPreviewConfig(raw); expect(config.services.app.seed).toEqual(['npm run db:migrate', 'npm run db:seed']); }); it('omits seed when not specified', () => { const raw = ` version: 1 services: app: build: "." port: 3000 `; const config = parseCwPreviewConfig(raw); expect(config.services.app.seed).toBeUndefined(); }); it('parses dev section with only image', () => { const raw = ` version: 1 services: app: build: "." port: 3000 dev: image: node:20-alpine `; const config = parseCwPreviewConfig(raw); expect(config.services.app.dev).toBeDefined(); expect(config.services.app.dev!.image).toBe('node:20-alpine'); expect(config.services.app.dev!.command).toBeUndefined(); expect(config.services.app.dev!.workdir).toBeUndefined(); }); });