77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import { load } from 'js-yaml';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
interface ComposeBuild {
|
|
context?: string;
|
|
args?: Record<string, string>;
|
|
}
|
|
|
|
interface ComposeService {
|
|
build?: string | ComposeBuild;
|
|
environment?: Record<string, string> | string[];
|
|
profiles?: string[];
|
|
}
|
|
|
|
interface ComposeConfig {
|
|
services: Record<string, ComposeService>;
|
|
}
|
|
|
|
const dockerfile = readFileSync(resolve(process.cwd(), 'Dockerfile'), 'utf8');
|
|
const composeSource = readFileSync(resolve(process.cwd(), 'docker-compose.yml'), 'utf8');
|
|
const compose = load(composeSource) as ComposeConfig;
|
|
|
|
function appService(role: 'learner' | 'ops' | 'server') {
|
|
const service = compose.services[role];
|
|
expect(service, `Compose must define the ${role} service`).toBeDefined();
|
|
expect(typeof service.build).toBe('object');
|
|
expect(Array.isArray(service.environment)).toBe(false);
|
|
|
|
return service as ComposeService & {
|
|
build: ComposeBuild;
|
|
environment: Record<string, string>;
|
|
};
|
|
}
|
|
|
|
describe('Docker deployment-role contract', () => {
|
|
it('makes the public deployment role available while Next.js builds the browser bundle', () => {
|
|
const builder = dockerfile.match(
|
|
/FROM base AS builder([\s\S]*?)FROM node:22-alpine AS runner/,
|
|
)?.[1];
|
|
|
|
expect(builder).toBeDefined();
|
|
expect(builder).toContain('ARG NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=learner');
|
|
expect(builder).toContain(
|
|
'ENV NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=$NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE',
|
|
);
|
|
expect(builder!.indexOf('ENV NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE')).toBeLessThan(
|
|
builder!.indexOf('RUN pnpm build'),
|
|
);
|
|
});
|
|
|
|
it('uses the build role as the fail-closed runtime default in the image', () => {
|
|
const runner = dockerfile.match(/FROM node:22-alpine AS runner([\s\S]*)/)?.[1];
|
|
|
|
expect(runner).toBeDefined();
|
|
expect(runner).toContain('ARG NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE=learner');
|
|
expect(runner).toContain('ENV OPENMAIC_DEPLOYMENT_ROLE=$NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE');
|
|
});
|
|
|
|
it.each(['learner', 'ops', 'server'] as const)(
|
|
'keeps the %s image build and runtime roles identical',
|
|
(role) => {
|
|
const service = appService(role);
|
|
|
|
expect(service.build.context).toBe('.');
|
|
expect(service.build.args?.NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE).toBe(role);
|
|
expect(service.environment.OPENMAIC_DEPLOYMENT_ROLE).toBe(role);
|
|
expect(service.environment.NEXT_PUBLIC_OPENMAIC_DEPLOYMENT_ROLE).toBe(role);
|
|
},
|
|
);
|
|
|
|
it('does not compile the server-only publishing credential into any role image', () => {
|
|
expect(composeSource).not.toContain('NEXT_PUBLIC_COURSEWARE_PUBLISH_TOKEN');
|
|
});
|
|
});
|