162 lines
5.1 KiB
TypeScript
162 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { validateRuntimeEnvironment } from './runtime-environment.js';
|
|
|
|
const validAppEnvironment = {
|
|
APP_ORIGIN: 'https://stage.shd.build',
|
|
ORIGIN: 'https://stage.shd.build',
|
|
PASSKEY_RP_ID: 'shd.build',
|
|
SCHEDULER_TOKEN: 'scheduler-secret-with-32-characters',
|
|
TDR_DATABASE_PATH: '/app/data/shd-build-staging.db',
|
|
TDR_AVATAR_STORAGE_PATH: '/app/data/avatars',
|
|
TDR_GAME_ICON_STORAGE_PATH: '/app/data/game-icons',
|
|
};
|
|
|
|
describe('validateRuntimeEnvironment', () => {
|
|
it('accepts the hosted app contract without optional providers', () => {
|
|
expect(validateRuntimeEnvironment('app', validAppEnvironment)).toEqual([]);
|
|
});
|
|
|
|
it('reports every missing required app value', () => {
|
|
expect(validateRuntimeEnvironment('app', {})).toEqual([
|
|
'APP_ORIGIN is required.',
|
|
'ORIGIN is required.',
|
|
'PASSKEY_RP_ID is required.',
|
|
'SCHEDULER_TOKEN is required.',
|
|
'TDR_DATABASE_PATH is required.',
|
|
'TDR_AVATAR_STORAGE_PATH is required.',
|
|
'TDR_GAME_ICON_STORAGE_PATH is required.',
|
|
]);
|
|
});
|
|
|
|
it('validates canonical origins and passkey hostname scope', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
APP_ORIGIN: 'https://stage.shd.build/sign-in',
|
|
}),
|
|
).toContain('APP_ORIGIN must contain only a URL origin.');
|
|
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
PASSKEY_RP_ID: 'example.com',
|
|
}),
|
|
).toContain(
|
|
'PASSKEY_RP_ID must equal APP_ORIGIN hostname or be its registrable parent domain.',
|
|
);
|
|
});
|
|
|
|
it('rejects mismatched app and adapter hostnames', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
ORIGIN: 'https://other.shd.build',
|
|
}),
|
|
).toContain('APP_ORIGIN and ORIGIN must use the same hostname.');
|
|
});
|
|
|
|
it('requires optional integration credentials only as pairs', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
DISCORD_CLIENT_ID: 'client-id',
|
|
TURNSTILE_SECRET_KEY: 'secret',
|
|
}),
|
|
).toEqual([
|
|
'DISCORD_CLIENT_ID and DISCORD_CLIENT_SECRET must be configured together.',
|
|
'TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY must be configured together.',
|
|
]);
|
|
});
|
|
|
|
it('allows SMTP defaults without enabling email and validates enabled SMTP', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
SMTP_PORT: '587',
|
|
SMTP_SECURE: 'false',
|
|
}),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
SMTP_HOST: 'smtp.example.com',
|
|
SMTP_USER: 'agent',
|
|
SMTP_PORT: '70000',
|
|
}),
|
|
).toEqual([
|
|
'SMTP_HOST and SMTP_FROM must be configured together.',
|
|
'SMTP_USER and SMTP_PASSWORD must be configured together.',
|
|
'SMTP_PORT must be an integer from 1 through 65535.',
|
|
]);
|
|
});
|
|
|
|
it('validates optional redirect URLs when supplied', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
DISCORD_REDIRECT_URI: 'ftp://stage.shd.build/callback',
|
|
}),
|
|
).toContain('DISCORD_REDIRECT_URI must be a valid HTTP or HTTPS URL.');
|
|
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
DISCORD_REDIRECT_URI: 'https://dev.shd.build/auth/discord/callback',
|
|
}),
|
|
).toContain(
|
|
'DISCORD_REDIRECT_URI must use APP_ORIGIN hostname and /auth/discord/callback.',
|
|
);
|
|
});
|
|
|
|
it('validates hosted paths and SMTP TLS configuration', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('app', {
|
|
...validAppEnvironment,
|
|
SMTP_SECURE: 'yes',
|
|
TDR_DATABASE_PATH: './data/staging.db',
|
|
}),
|
|
).toEqual([
|
|
'SMTP_SECURE must be true or false.',
|
|
'TDR_DATABASE_PATH must be an absolute hosted path.',
|
|
]);
|
|
});
|
|
|
|
it('accepts a configured scheduler and rejects missing or invalid values', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('scheduler', {
|
|
SCHEDULER_URL: 'http://app:3000/api/internal/scheduler',
|
|
SCHEDULER_TOKEN: 'scheduler-secret-with-32-characters',
|
|
}),
|
|
).toEqual([]);
|
|
|
|
expect(
|
|
validateRuntimeEnvironment('scheduler', {
|
|
SCHEDULER_URL: 'not a URL',
|
|
}),
|
|
).toEqual([
|
|
'SCHEDULER_TOKEN is required.',
|
|
'SCHEDULER_URL must be a valid HTTP or HTTPS URL.',
|
|
'SCHEDULER_URL must use the internal app scheduler endpoint.',
|
|
]);
|
|
});
|
|
|
|
it('requires a strong scheduler token and fixed internal endpoint', () => {
|
|
expect(
|
|
validateRuntimeEnvironment('scheduler', {
|
|
SCHEDULER_URL: 'https://example.com/api/internal/scheduler',
|
|
SCHEDULER_TOKEN: 'too-short',
|
|
}),
|
|
).toEqual([
|
|
'SCHEDULER_URL must use the internal app scheduler endpoint.',
|
|
'SCHEDULER_TOKEN must contain at least 32 non-whitespace characters.',
|
|
]);
|
|
});
|
|
|
|
it('rejects unsupported runtime modes', () => {
|
|
expect(() =>
|
|
validateRuntimeEnvironment('worker' as 'app', validAppEnvironment),
|
|
).toThrow('Unsupported runtime mode: worker');
|
|
});
|
|
});
|