test: add timestampPdf signature tests with sample.pdf

Test the timestampPdf() function using the real sample.pdf (SHA256 verified).
PdfSigner is mocked to avoid network calls while validating:
- Correct signdate/TSA URL passed to PdfSigner
- PDF bytes forwarded to signer.sign()
- Uint8Array returned from signed result
- Error propagation from TSA failures
- Multiple TSA URL support
- Original PDF bytes not mutated
This commit is contained in:
InstalZDLL
2026-03-15 15:44:55 +01:00
parent 1569b66d5c
commit 969b74dfb5
2 changed files with 113 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
const mockSign = vi.fn();
vi.mock('zgapdfsigner', () => {
const MockPdfSigner = vi.fn(function (this: { sign: typeof mockSign }) {
this.sign = mockSign;
});
return { PdfSigner: MockPdfSigner };
});
import { PdfSigner } from 'zgapdfsigner';
import { timestampPdf } from '@/js/logic/digital-sign-pdf';
const SAMPLE_PDF_PATH = path.resolve(__dirname, './fixtures/sample.pdf');
const SAMPLE_PDF_SHA256 =
'229defbb0cee6f02673a5cde290d0673e75a0dc31cec43989c8ab2a4eca7e1bb';
async function sha256(data: Uint8Array): Promise<string> {
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
}
describe('timestampPdf', () => {
let samplePdfBytes: Uint8Array;
beforeEach(() => {
vi.clearAllMocks();
samplePdfBytes = new Uint8Array(fs.readFileSync(SAMPLE_PDF_PATH));
});
it('should load the correct sample PDF', async () => {
const hash = await sha256(samplePdfBytes);
expect(hash).toBe(SAMPLE_PDF_SHA256);
});
it('should call PdfSigner with signdate option containing the TSA URL', async () => {
const fakeSigned = new Uint8Array([80, 68, 70, 45, 49, 46, 52]); // "PDF-1.4"
mockSign.mockResolvedValueOnce(fakeSigned);
const tsaUrl = 'http://timestamp.digicert.com';
await timestampPdf(samplePdfBytes, tsaUrl);
expect(PdfSigner).toHaveBeenCalledWith({
signdate: { url: tsaUrl },
});
});
it('should pass the PDF bytes to signer.sign()', async () => {
const fakeSigned = new Uint8Array([1, 2, 3]);
mockSign.mockResolvedValueOnce(fakeSigned);
const tsaUrl = 'http://timestamp.digicert.com';
await timestampPdf(samplePdfBytes, tsaUrl);
expect(mockSign).toHaveBeenCalledOnce();
const passedBytes = mockSign.mock.calls[0][0];
expect(passedBytes).toBeInstanceOf(Uint8Array);
expect(passedBytes.length).toBe(samplePdfBytes.length);
});
it('should return a Uint8Array from the signed result', async () => {
const fakeSigned = new Uint8Array([10, 20, 30, 40]);
mockSign.mockResolvedValueOnce(fakeSigned);
const result = await timestampPdf(samplePdfBytes, 'http://ts.ssl.com');
expect(result).toBeInstanceOf(Uint8Array);
expect(result).toEqual(new Uint8Array([10, 20, 30, 40]));
});
it('should propagate errors from PdfSigner.sign()', async () => {
mockSign.mockRejectedValueOnce(new Error('TSA server unreachable'));
await expect(
timestampPdf(samplePdfBytes, 'http://invalid-tsa.example.com')
).rejects.toThrow('TSA server unreachable');
});
it('should work with different TSA URLs', async () => {
const fakeSigned = new Uint8Array([1]);
mockSign.mockResolvedValue(fakeSigned);
const urls = [
'http://timestamp.digicert.com',
'http://timestamp.sectigo.com',
'https://freetsa.org/tsr',
];
for (const url of urls) {
vi.mocked(PdfSigner).mockClear();
await timestampPdf(samplePdfBytes, url);
expect(PdfSigner).toHaveBeenCalledWith({
signdate: { url },
});
}
});
it('should not modify the original PDF bytes', async () => {
const fakeSigned = new Uint8Array([1, 2, 3]);
mockSign.mockResolvedValueOnce(fakeSigned);
const originalCopy = new Uint8Array(samplePdfBytes);
await timestampPdf(samplePdfBytes, 'http://timestamp.digicert.com');
expect(samplePdfBytes).toEqual(originalCopy);
});
});

BIN
src/tests/fixtures/sample.pdf vendored Normal file

Binary file not shown.