test(e2e): type the specs against the schema and types they run on

packages/app-tests has no typecheck script, and 10 errors had collected in
its specs. None changes what a spec sends or checks:

- Two API specs map over create-many's JSON, which Playwright types as any,
  so the callback's parameters were implicitly any. The responses are typed
  as TCreateEnvelopeRecipientsResponse, as the file already types its other
  responses.
- A PDF built with Buffer.from(Uint8Array) is a Buffer<ArrayBuffer>; its
  helpers said Buffer, which @types/node 26 reads as possibly shared memory,
  and a Blob takes no shared memory.
- The seed's updateDocumentOptions was typed as Prisma's unchecked update
  input, which has no relation fields, while the dictation specs upsert
  documentMeta through it — which Prisma takes as the checked input. It is
  typed as EnvelopeUpdateInput; every caller passes fields that input has.
- A spec typed a seeded document as Document, a model the schema replaced
  with Envelope.
- The alignment spec picked each field's type and fieldMeta apart, which
  loses which meta goes with which type; it drops the two keys the payload
  does not take and spreads the rest, and types the payload as the schema's
  input, which is what a client sends.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-19 04:57:58 -07:00
co-authored by Hanzo Dev
parent 7dd57063ba
commit 1493fae287
5 changed files with 36 additions and 34 deletions
@@ -25,7 +25,10 @@ import type {
TCreateEnvelopeResponse,
} from '@hanzo/esign-trpc/server/envelope-router/create-envelope.types';
import type { TDistributeEnvelopeRequest } from '@hanzo/esign-trpc/server/envelope-router/distribute-envelope.types';
import type { TCreateEnvelopeRecipientsRequest } from '@hanzo/esign-trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
import type {
TCreateEnvelopeRecipientsRequest,
TCreateEnvelopeRecipientsResponse,
} from '@hanzo/esign-trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
import type { TUpdateEnvelopeRecipientsRequest } from '@hanzo/esign-trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types';
import type { TFindEnvelopesResponse } from '@hanzo/esign-trpc/server/envelope-router/find-envelopes.types';
import type { TGetEnvelopeResponse } from '@hanzo/esign-trpc/server/envelope-router/get-envelope.types';
@@ -813,7 +816,8 @@ test.describe('API V2 Envelopes', () => {
expect(createRecipientsRes.ok()).toBeTruthy();
expect(createRecipientsRes.status()).toBe(200);
const recipientsResponse = await createRecipientsRes.json();
const recipientsResponse: TCreateEnvelopeRecipientsResponse =
await createRecipientsRes.json();
const recipient = recipientsResponse.data[0];
expect(recipient.email).toBe('');
@@ -903,7 +907,8 @@ test.describe('API V2 Envelopes', () => {
});
expect(createRecipientsRes.ok()).toBeTruthy();
const recipientsResponse = await createRecipientsRes.json();
const recipientsResponse: TCreateEnvelopeRecipientsResponse =
await createRecipientsRes.json();
const recipient = recipientsResponse.data[0];
expect(recipient.email).toBe('');
@@ -1047,7 +1052,8 @@ test.describe('API V2 Envelopes', () => {
});
expect(createRecipientsRes.ok()).toBeTruthy();
const recipientsResponse = await createRecipientsRes.json();
const recipientsResponse: TCreateEnvelopeRecipientsResponse =
await createRecipientsRes.json();
const recipients = recipientsResponse.data;
expect(recipients.length).toBe(4);
@@ -1142,7 +1148,8 @@ test.describe('API V2 Envelopes', () => {
});
expect(createRecipientsRes.ok()).toBeTruthy();
const recipientsResponse = await createRecipientsRes.json();
const recipientsResponse: TCreateEnvelopeRecipientsResponse =
await createRecipientsRes.json();
const recipients = recipientsResponse.data;
// Get envelope to assign fields
@@ -1247,7 +1254,8 @@ test.describe('API V2 Envelopes', () => {
});
expect(createRecipientsRes.ok()).toBeTruthy();
const recipientsResponse = await createRecipientsRes.json();
const recipientsResponse: TCreateEnvelopeRecipientsResponse =
await createRecipientsRes.json();
const recipient = recipientsResponse.data[0];
// Get envelope to assign fields
@@ -145,7 +145,7 @@ test.describe('Placeholder-based field creation', () => {
/**
* Create a PDF with the same placeholder appearing multiple times at different locations.
*/
const createPdfWithDuplicatePlaceholders = async (): Promise<Buffer> => {
const createPdfWithDuplicatePlaceholders = async (): Promise<Buffer<ArrayBuffer>> => {
const pdf = PDF.create();
const page = pdf.addPage({ size: 'letter' });
@@ -161,7 +161,7 @@ test.describe('Placeholder-based field creation', () => {
const createEnvelopeWithPdfBuffer = async (
request: APIRequestContext,
pdfBuffer: Buffer,
pdfBuffer: Buffer<ArrayBuffer>,
filename: string,
): Promise<TCreateEnvelopeResponse> => {
const formData = new FormData();
@@ -1,5 +1,5 @@
import { type Page, expect, test } from '@playwright/test';
import type { Document, Team } from '@prisma/client';
import type { Envelope, Team } from '@prisma/client';
import { PDF_VIEWER_PAGE_SELECTOR } from '@hanzo/esign-lib/constants/pdf-viewer';
import { prisma } from '@hanzo/esign-prisma';
@@ -15,7 +15,7 @@ import { signSignaturePad } from '../fixtures/signature';
const completeDocumentFlowWithDuplicateRecipients = async (options: {
page: Page;
team: Team;
document: Document;
document: Envelope;
}) => {
const { page, team, document } = options;
@@ -7,6 +7,7 @@ import path from 'node:path';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
import pixelMatch from 'pixelmatch';
import { PNG } from 'pngjs';
import type { z } from 'zod';
import { getEnvelopeItemPdfUrl } from '@hanzo/esign-lib/utils/envelope-download';
import { prisma } from '@hanzo/esign-prisma';
@@ -18,8 +19,8 @@ import { isBase64Image } from '../../../lib/constants/signatures';
import { createApiToken } from '../../../lib/server-only/public-api/create-api-token';
import { RecipientRole } from '../../../prisma/generated/types';
import type {
TCreateEnvelopePayload,
TCreateEnvelopeResponse,
ZCreateEnvelopePayloadSchema,
} from '../../../trpc/server/envelope-router/create-envelope.types';
import type { TDistributeEnvelopeRequest } from '../../../trpc/server/envelope-router/distribute-envelope.types';
import { ALIGNMENT_TEST_FIELDS } from '../../constants/field-alignment-pdf';
@@ -77,29 +78,22 @@ test('field placement visual regression', async ({ page, request }, testInfo) =>
const formData = new FormData();
const fieldMetaFields = FIELD_META_TEST_FIELDS.map((field) => ({
identifier: 'field-meta',
type: field.type,
page: field.page,
positionX: field.positionX,
positionY: field.positionY,
width: field.width,
height: field.height,
fieldMeta: field.fieldMeta,
}));
const fieldMetaFields = FIELD_META_TEST_FIELDS.map(
({ customText: _customText, signature: _signature, ...field }) => ({
identifier: 'field-meta',
...field,
}),
);
const alignmentFields = ALIGNMENT_TEST_FIELDS.map((field) => ({
identifier: 'alignment-pdf',
type: field.type,
page: field.page,
positionX: field.positionX,
positionY: field.positionY,
width: field.width,
height: field.height,
fieldMeta: field.fieldMeta,
}));
const alignmentFields = ALIGNMENT_TEST_FIELDS.map(
({ customText: _customText, signature: _signature, ...field }) => ({
identifier: 'alignment-pdf',
...field,
}),
);
const createEnvelopePayload: TCreateEnvelopePayload = {
// What the client sends: the schema's input, before the server fills in defaults.
const createEnvelopePayload: z.input<typeof ZCreateEnvelopePayloadSchema> = {
type: EnvelopeType.DOCUMENT,
title: 'Envelope Full Field Test',
recipients: [
+2 -2
View File
@@ -480,7 +480,7 @@ export const seedPendingDocumentNoFields = async ({
owner: User;
recipients: (User | string)[];
teamId: number;
updateDocumentOptions?: Partial<Prisma.EnvelopeUncheckedUpdateInput>;
updateDocumentOptions?: Partial<Prisma.EnvelopeUpdateInput>;
}) => {
const document = await seedBlankDocument(owner, teamId);
@@ -537,7 +537,7 @@ export const seedPendingDocumentWithFullFields = async ({
owner: User;
recipients: (User | string)[];
recipientsCreateOptions?: Partial<Prisma.RecipientUncheckedCreateInput>[];
updateDocumentOptions?: Partial<Prisma.EnvelopeUncheckedUpdateInput>;
updateDocumentOptions?: Partial<Prisma.EnvelopeUpdateInput>;
fields?: FieldType[];
teamId: number;
}) => {