Run project-specific initialization commands (DB migrations, fixture loading, etc.) automatically after containers are healthy, before the preview is marked ready. Configured via per-service `seed` arrays in .cw-preview.yml.
180 lines
4.4 KiB
TypeScript
180 lines
4.4 KiB
TypeScript
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();
|
|
});
|
|
});
|