132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
|
|
|
||
|
|
// Validation-path tests for uploadDocumentForSigning. The
|
||
|
|
// heavy-integration paths (storage put, Documenso round-trip, signer
|
||
|
|
// updates) are exercised by Playwright realapi specs; these tests
|
||
|
|
// pin the input-validation contract so a regression in the
|
||
|
|
// recipient/field/PDF guards is caught at unit-test time.
|
||
|
|
|
||
|
|
// Stub the heavy dependencies BEFORE importing the service so its
|
||
|
|
// module-level imports resolve to the stubs.
|
||
|
|
vi.mock('@/lib/db', () => ({
|
||
|
|
db: {
|
||
|
|
query: {
|
||
|
|
interests: {
|
||
|
|
findFirst: vi.fn().mockResolvedValue({ id: 'int-1', portId: 'port-1', clientId: 'c-1' }),
|
||
|
|
},
|
||
|
|
ports: { findFirst: vi.fn().mockResolvedValue({ id: 'port-1', name: 'Test Port' }) },
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
|
||
|
|
vi.mock('@/lib/services/berth-pdf-parser', () => ({
|
||
|
|
isPdfMagic: (b: Buffer) => b.slice(0, 5).toString() === '%PDF-',
|
||
|
|
}));
|
||
|
|
|
||
|
|
import {
|
||
|
|
uploadDocumentForSigning,
|
||
|
|
type CustomDocumentRecipient,
|
||
|
|
} from '@/lib/services/custom-document-upload.service';
|
||
|
|
|
||
|
|
const PDF_HEADER = Buffer.from('%PDF-1.7\n');
|
||
|
|
const NON_PDF = Buffer.from('this is not a PDF');
|
||
|
|
|
||
|
|
const baseArgs = {
|
||
|
|
interestId: 'int-1',
|
||
|
|
portId: 'port-1',
|
||
|
|
portSlug: 'test-port',
|
||
|
|
documentType: 'contract' as const,
|
||
|
|
title: 'Sales Contract',
|
||
|
|
pdfBuffer: PDF_HEADER,
|
||
|
|
filename: 'contract.pdf',
|
||
|
|
recipients: [
|
||
|
|
{ name: 'Buyer', email: 'buyer@example.com', role: 'SIGNER', signingOrder: 1 },
|
||
|
|
{ name: 'Seller', email: 'seller@example.com', role: 'SIGNER', signingOrder: 2 },
|
||
|
|
] satisfies CustomDocumentRecipient[],
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
recipientIndex: 0,
|
||
|
|
type: 'SIGNATURE' as const,
|
||
|
|
pageNumber: 1,
|
||
|
|
pageX: 10,
|
||
|
|
pageY: 80,
|
||
|
|
pageWidth: 30,
|
||
|
|
pageHeight: 5,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
meta: {
|
||
|
|
userId: 'user-1',
|
||
|
|
portId: 'port-1',
|
||
|
|
ipAddress: '127.0.0.1',
|
||
|
|
userAgent: 'test',
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
describe('uploadDocumentForSigning validation', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
vi.clearAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects empty recipient list', async () => {
|
||
|
|
await expect(uploadDocumentForSigning({ ...baseArgs, recipients: [] })).rejects.toThrow(
|
||
|
|
/at least one recipient/i,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects empty field list', async () => {
|
||
|
|
await expect(uploadDocumentForSigning({ ...baseArgs, fields: [] })).rejects.toThrow(
|
||
|
|
/at least one field/i,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects empty PDF buffer', async () => {
|
||
|
|
await expect(
|
||
|
|
uploadDocumentForSigning({ ...baseArgs, pdfBuffer: Buffer.alloc(0) }),
|
||
|
|
).rejects.toThrow(/PDF buffer is empty/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects oversized PDF', async () => {
|
||
|
|
const oversized = Buffer.alloc(51 * 1024 * 1024, 0x20);
|
||
|
|
oversized.write('%PDF-1.7', 0);
|
||
|
|
await expect(uploadDocumentForSigning({ ...baseArgs, pdfBuffer: oversized })).rejects.toThrow(
|
||
|
|
/exceeds.*MB cap/i,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects non-PDF magic bytes', async () => {
|
||
|
|
await expect(uploadDocumentForSigning({ ...baseArgs, pdfBuffer: NON_PDF })).rejects.toThrow(
|
||
|
|
/not a PDF/,
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects out-of-range recipientIndex on a field', async () => {
|
||
|
|
await expect(
|
||
|
|
uploadDocumentForSigning({
|
||
|
|
...baseArgs,
|
||
|
|
fields: [{ ...baseArgs.fields[0]!, recipientIndex: 5 }],
|
||
|
|
}),
|
||
|
|
).rejects.toThrow(/out of range/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects negative recipientIndex on a field', async () => {
|
||
|
|
await expect(
|
||
|
|
uploadDocumentForSigning({
|
||
|
|
...baseArgs,
|
||
|
|
fields: [{ ...baseArgs.fields[0]!, recipientIndex: -1 }],
|
||
|
|
}),
|
||
|
|
).rejects.toThrow(/out of range/);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects duplicate signingOrder across recipients', async () => {
|
||
|
|
await expect(
|
||
|
|
uploadDocumentForSigning({
|
||
|
|
...baseArgs,
|
||
|
|
recipients: [
|
||
|
|
{ name: 'A', email: 'a@x.com', role: 'SIGNER', signingOrder: 1 },
|
||
|
|
{ name: 'B', email: 'b@x.com', role: 'SIGNER', signingOrder: 1 },
|
||
|
|
],
|
||
|
|
}),
|
||
|
|
).rejects.toThrow(/Duplicate signingOrder/);
|
||
|
|
});
|
||
|
|
});
|