Bundles the prior autonomous-session output that was sitting unstaged: - Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances) - country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk after the per-subpath dynamic-import approach silently failed in webpack) - Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index, redirects (ocr to ai, reports to dashboard, invitations to users), docs/admin-ia-proposal.md - Per-template email tester (registry + endpoint + UI on Email admin page) - Cancel-document mode picker (delete-from-Documenso vs keep-for-audit) - Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers - Customize-widgets per-region sortables at xl+ (charts/rails/feed); single flat sortable below xl when the layout stacks; per-viewport saved orders - Audit doc updates capturing each shipped item - Lint fixes: react-compiler immutability in DonutChart (reduce instead of let-reassign), set-state-in-effect disables in CountryFlag and UploadForSigning preview-bytes effect, unused 'confirm' destructures in interest contract + reservation tabs, unescaped apostrophe in test-template card copy
96 lines
3.2 KiB
TypeScript
96 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
|
|
import {
|
|
DOC_TYPE_LABEL,
|
|
extractSigningToken,
|
|
nextPendingSigner,
|
|
} from '@/lib/services/documenso-signers';
|
|
|
|
describe('extractSigningToken', () => {
|
|
it('pulls the last path segment when it looks token-shaped', () => {
|
|
expect(extractSigningToken('https://sig.example.com/sign/vbT8hi3jKQmrFP_LN1WcS')).toBe(
|
|
'vbT8hi3jKQmrFP_LN1WcS',
|
|
);
|
|
});
|
|
|
|
it('handles trailing slash gracefully', () => {
|
|
expect(extractSigningToken('https://sig.example.com/sign/HkrptwS42ZBXdRKj1TyUo/')).toBe(
|
|
'HkrptwS42ZBXdRKj1TyUo',
|
|
);
|
|
});
|
|
|
|
it('returns null for non-URL input', () => {
|
|
expect(extractSigningToken('not a url at all')).toBeNull();
|
|
expect(extractSigningToken('')).toBeNull();
|
|
expect(extractSigningToken(null)).toBeNull();
|
|
expect(extractSigningToken(undefined)).toBeNull();
|
|
});
|
|
|
|
it('rejects too-short tails (defends against generic "sign" / "embed" terminators)', () => {
|
|
expect(extractSigningToken('https://example.com/sign')).toBeNull();
|
|
expect(extractSigningToken('https://example.com/abc')).toBeNull();
|
|
});
|
|
|
|
it('rejects path tails with disallowed characters', () => {
|
|
// Real tokens are URL-safe base64 - no spaces, no punctuation
|
|
expect(extractSigningToken('https://example.com/sign/has%20space')).toBeNull();
|
|
});
|
|
|
|
it('handles real v1.32 + v2 token shapes', () => {
|
|
// Documenso 1.32 token (21-char URL-safe alphabet from real webhook docs)
|
|
expect(extractSigningToken('https://sig.documenso.com/sign/vbT8hi3jKQmrFP_LN1WcS')).toBe(
|
|
'vbT8hi3jKQmrFP_LN1WcS',
|
|
);
|
|
// Mixed-case + underscores + dashes
|
|
expect(extractSigningToken('https://app.example.com/sign/Aa_-Zz09Aa_-Zz09')).toBe(
|
|
'Aa_-Zz09Aa_-Zz09',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('DOC_TYPE_LABEL', () => {
|
|
it('maps the three known document types to customer-facing labels', () => {
|
|
expect(DOC_TYPE_LABEL.eoi).toBe('Expression of Interest');
|
|
expect(DOC_TYPE_LABEL.contract).toBe('Sales Contract');
|
|
expect(DOC_TYPE_LABEL.reservation_agreement).toBe('Reservation Agreement');
|
|
});
|
|
});
|
|
|
|
describe('nextPendingSigner', () => {
|
|
it('picks the lowest signingOrder pending signer', () => {
|
|
const signers = [
|
|
{ status: 'signed', signingOrder: 1 },
|
|
{ status: 'pending', signingOrder: 2 },
|
|
{ status: 'pending', signingOrder: 3 },
|
|
];
|
|
expect(nextPendingSigner(signers)).toEqual({ status: 'pending', signingOrder: 2 });
|
|
});
|
|
|
|
it('returns null when every signer has signed', () => {
|
|
expect(
|
|
nextPendingSigner([
|
|
{ status: 'signed', signingOrder: 1 },
|
|
{ status: 'signed', signingOrder: 2 },
|
|
]),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('treats declined the same as no longer pending', () => {
|
|
expect(
|
|
nextPendingSigner([
|
|
{ status: 'declined', signingOrder: 1 },
|
|
{ status: 'signed', signingOrder: 2 },
|
|
]),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('handles unordered input by signingOrder', () => {
|
|
const signers = [
|
|
{ status: 'pending', signingOrder: 5 },
|
|
{ status: 'signed', signingOrder: 1 },
|
|
{ status: 'pending', signingOrder: 2 },
|
|
];
|
|
expect(nextPendingSigner(signers)).toEqual({ status: 'pending', signingOrder: 2 });
|
|
});
|
|
});
|