118 lines
3.9 KiB
JavaScript
118 lines
3.9 KiB
JavaScript
import { spawnSync } from 'node:child_process';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const mocks = vi.hoisted(() => ({
|
|
env: { PDF_REGENERATE: 'never' },
|
|
files: new Map(),
|
|
}));
|
|
|
|
vi.mock('../src/config/env.js', () => ({ env: mocks.env }));
|
|
vi.mock('../src/pdf/render.js', () => ({
|
|
renderPdf: vi.fn(async (html) => Buffer.from(`pdf:${html}`)),
|
|
}));
|
|
vi.mock('../src/services/storage.js', () => ({
|
|
getObject: vi.fn(async (key) => {
|
|
if (!mocks.files.has(key)) {
|
|
throw new Error('missing');
|
|
}
|
|
return mocks.files.get(key);
|
|
}),
|
|
putObject: vi.fn(async (key, body) => {
|
|
mocks.files.set(key, Buffer.from(body));
|
|
}),
|
|
}));
|
|
|
|
const { refreshStoredPdf, rememberPdfSource } = await import('../src/services/pdfCache.js');
|
|
const { renderPdf } = await import('../src/pdf/render.js');
|
|
|
|
const KEY = 'applications/abc/consent.pdf';
|
|
|
|
describe('PDF_REGENERATE', () => {
|
|
afterEach(() => {
|
|
mocks.files.clear();
|
|
vi.mocked(renderPdf).mockClear();
|
|
});
|
|
|
|
it('never rebuilds a stored PDF by default', async () => {
|
|
mocks.env.PDF_REGENERATE = 'never';
|
|
const build = vi.fn(async () => '<p>v2</p>');
|
|
|
|
expect(await refreshStoredPdf(KEY, build)).toBeNull();
|
|
expect(build).not.toHaveBeenCalled();
|
|
expect(renderPdf).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('rebuilds only when the html changed in changed mode', async () => {
|
|
mocks.env.PDF_REGENERATE = 'changed';
|
|
await rememberPdfSource(KEY, '<p>v1</p>');
|
|
|
|
expect(await refreshStoredPdf(KEY, async () => '<p>v1</p>')).toBeNull();
|
|
expect(renderPdf).not.toHaveBeenCalled();
|
|
|
|
const rebuilt = await refreshStoredPdf(KEY, async () => '<p>v2</p>');
|
|
expect(String(rebuilt)).toBe('pdf:<p>v2</p>');
|
|
expect(String(mocks.files.get(KEY))).toBe('pdf:<p>v2</p>');
|
|
|
|
expect(await refreshStoredPdf(KEY, async () => '<p>v2</p>')).toBeNull();
|
|
expect(renderPdf).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('rebuilds a PDF with no recorded source in changed mode', async () => {
|
|
mocks.env.PDF_REGENERATE = 'changed';
|
|
mocks.files.set(KEY, Buffer.from('old pdf'));
|
|
|
|
expect(String(await refreshStoredPdf(KEY, async () => '<p>v1</p>'))).toBe('pdf:<p>v1</p>');
|
|
});
|
|
|
|
it('rebuilds on every call in always mode', async () => {
|
|
mocks.env.PDF_REGENERATE = 'always';
|
|
await refreshStoredPdf(KEY, async () => '<p>v1</p>');
|
|
await refreshStoredPdf(KEY, async () => '<p>v1</p>');
|
|
|
|
expect(renderPdf).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it('shares one render between simultaneous requests for the same file', async () => {
|
|
mocks.env.PDF_REGENERATE = 'always';
|
|
const build = vi.fn(async () => '<p>v1</p>');
|
|
|
|
await Promise.all([refreshStoredPdf(KEY, build), refreshStoredPdf(KEY, build)]);
|
|
|
|
expect(build).toHaveBeenCalledTimes(1);
|
|
expect(renderPdf).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('PDF_REGENERATE in production', () => {
|
|
const envModule = pathToFileURL(
|
|
path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../src/config/env.js'),
|
|
).href;
|
|
|
|
/**
|
|
* Runs outside backend/ so a developer's backend/.env is not loaded.
|
|
* @param {Record<string, string>} extra
|
|
*/
|
|
function loadEnv(extra) {
|
|
return spawnSync(
|
|
process.execPath,
|
|
['--input-type=module', '-e', `await import(${JSON.stringify(envModule)})`],
|
|
{ cwd: tmpdir(), env: { PATH: process.env.PATH, ...extra }, encoding: 'utf8' },
|
|
);
|
|
}
|
|
|
|
it('refuses to start with regeneration enabled', () => {
|
|
const result = loadEnv({ NODE_ENV: 'production', PDF_REGENERATE: 'changed' });
|
|
|
|
expect(result.status).toBe(1);
|
|
expect(result.stderr).toContain('PDF_REGENERATE=changed is not allowed in production');
|
|
});
|
|
|
|
it('starts with regeneration off', () => {
|
|
expect(loadEnv({ NODE_ENV: 'production', PDF_REGENERATE: 'never' }).status).toBe(0);
|
|
expect(loadEnv({ NODE_ENV: 'production' }).status).toBe(0);
|
|
});
|
|
});
|