Files
pdfme/packages/cli/__tests__/contract.test.ts
Kyohei Fukuda 03f616f0c5 [codex] Add @pdfme/jsx package (#1466)
* feat(jsx): add pdfme jsx package

* refactor(jsx): derive text props from schema types

* fix(jsx): tighten mvp layout constraints

* feat(jsx): measure text height with schema helpers

* docs: update jsx md2pdf roadmap
2026-05-05 17:48:19 +09:00

151 lines
4.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { execFileSync } from 'node:child_process';
import { join, dirname } from 'node:path';
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { PDFDocument } from '@pdfme/pdf-lib';
import { a4BasePdf } from './helpers.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const CLI = join(__dirname, '..', 'dist', 'index.js');
const TMP = join(__dirname, '..', '.test-tmp-contract');
function runCli(
args: string[],
options: { input?: string; env?: NodeJS.ProcessEnv } = {},
): { stdout: string; stderr: string; exitCode: number } {
try {
const stdout = execFileSync('node', [CLI, ...args], {
encoding: 'utf8',
timeout: 30000,
input: options.input,
env: options.env,
});
return { stdout, stderr: '', exitCode: 0 };
} catch (error: any) {
return {
stdout: error.stdout ?? '',
stderr: error.stderr ?? '',
exitCode: error.status ?? 1,
};
}
}
describe('CLI contract failures', () => {
beforeAll(() => {
mkdirSync(TMP, { recursive: true });
});
afterAll(() => {
rmSync(TMP, { recursive: true, force: true });
});
it.each([
{ name: 'generate', args: ['generate', '--bogus', '--json'] },
{ name: 'validate', args: ['validate', '--bogus', '--json'] },
{ name: 'pdf2img', args: ['pdf2img', '--bogus', '--json'] },
{ name: 'pdf2size', args: ['pdf2size', '--bogus', '--json'] },
{ name: 'examples', args: ['examples', '--bogus', '--json'] },
])('returns structured EARG for unknown flags: $name', ({ args }) => {
const result = runCli(args);
expect(result.exitCode).toBe(1);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EARG');
expect(parsed.error.message).toContain('Unknown argument');
});
it('reports malformed unified job JSON with structured EIO', () => {
const jobPath = join(TMP, 'malformed-job.json');
writeFileSync(jobPath, '{"template": ');
const result = runCli(['generate', jobPath, '-o', join(TMP, 'out.pdf'), '--json']);
expect(result.exitCode).toBe(3);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EIO');
expect(parsed.error.message).toContain('Failed to parse JSON file');
});
it('reports malformed stdin JSON for validate with structured EIO', () => {
const result = runCli(['validate', '-', '--json'], {
input: '{"basePdf": ',
});
expect(result.exitCode).toBe(3);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EIO');
expect(parsed.error.message).toContain('Failed to parse JSON from stdin');
});
it('returns structured EARG when generate has no input source', () => {
const result = runCli(['generate', '-o', join(TMP, 'no-input.pdf'), '--json']);
expect(result.exitCode).toBe(1);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EARG');
expect(parsed.error.message).toContain('No input provided');
});
it('rejects invalid image format enum for generate', () => {
const jobPath = join(TMP, 'invalid-image-format.json');
writeFileSync(
jobPath,
JSON.stringify({
template: {
basePdf: a4BasePdf(),
schemas: [
[
{
name: 'title',
type: 'text',
position: { x: 20, y: 20 },
width: 100,
height: 10,
},
],
],
},
inputs: [{ title: 'Hello' }],
}),
);
const result = runCli([
'generate',
jobPath,
'--imageFormat',
'gif',
'-o',
join(TMP, 'invalid-image-format.pdf'),
'--json',
]);
expect(result.exitCode).toBe(1);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EARG');
expect(parsed.error.message).toContain('--imageFormat');
});
it('rejects pdf2img output paths that are not directories', async () => {
const pdfPath = join(TMP, 'sample.pdf');
const outputPath = join(TMP, 'not-a-directory.txt');
const pdfDoc = await PDFDocument.create();
pdfDoc.addPage([595.28, 841.89]);
writeFileSync(pdfPath, await pdfDoc.save());
writeFileSync(outputPath, 'existing file');
const result = runCli(['pdf2img', pdfPath, '-o', outputPath, '--json']);
expect(result.exitCode).toBe(3);
const parsed = JSON.parse(result.stdout);
expect(parsed.ok).toBe(false);
expect(parsed.error.code).toBe('EIO');
expect(parsed.error.message).toContain('Output path must be a directory');
});
});