mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
Compare commits
2 Commits
4a0425b120
...
bbf1dd3c6b
| Author | SHA1 | Date | |
|---|---|---|---|
| bbf1dd3c6b | |||
| c10c95ca00 |
@ -2,20 +2,27 @@ import { expect, test } from '@playwright/test';
|
|||||||
import type { Team, User } from '@prisma/client';
|
import type { Team, User } from '@prisma/client';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { pick } from 'remeda';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { incrementDocumentId } from '@documenso/lib/server-only/envelope/increment-id';
|
|
||||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||||
import { prefixedId } from '@documenso/lib/universal/id';
|
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import {
|
import {
|
||||||
DocumentSource,
|
DocumentDistributionMethod,
|
||||||
|
DocumentSigningOrder,
|
||||||
|
DocumentStatus,
|
||||||
DocumentVisibility,
|
DocumentVisibility,
|
||||||
EnvelopeType,
|
EnvelopeType,
|
||||||
|
FieldType,
|
||||||
|
FolderType,
|
||||||
RecipientRole,
|
RecipientRole,
|
||||||
} from '@documenso/prisma/client';
|
} from '@documenso/prisma/client';
|
||||||
import { seedUser } from '@documenso/prisma/seed/users';
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
import type { TCreateEnvelopeItemsRequest } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
import type { TCreateEnvelopeItemsRequest } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
|
||||||
|
import type {
|
||||||
|
TCreateEnvelopePayload,
|
||||||
|
TCreateEnvelopeResponse,
|
||||||
|
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||||
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
|
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
|
||||||
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
|
import type { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
|
||||||
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
|
import type { TUpdateEnvelopeRequest } from '@documenso/trpc/server/envelope-router/update-envelope.types';
|
||||||
@ -51,64 +58,354 @@ test.describe('API V2 Envelopes', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Envelope create endpoint', () => {
|
||||||
|
test('should fail on invalid form', async ({ request }) => {
|
||||||
|
const payload = {
|
||||||
|
type: 'Invalid Type',
|
||||||
|
title: 'Test Envelope',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenB}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeFalsy();
|
||||||
|
expect(res.status()).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create envelope with single file', async ({ request }) => {
|
||||||
|
const payload = {
|
||||||
|
type: EnvelopeType.TEMPLATE,
|
||||||
|
title: 'Test Envelope',
|
||||||
|
} satisfies TCreateEnvelopePayload;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
name: 'field-font-alignment.pdf',
|
||||||
|
data: fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
formData.append('files', new File([file.data], file.name, { type: 'application/pdf' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenB}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
|
||||||
|
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||||
|
|
||||||
|
const envelope = await prisma.envelope.findUnique({
|
||||||
|
where: {
|
||||||
|
id: response.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
envelopeItems: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(envelope).toBeDefined();
|
||||||
|
expect(envelope?.title).toBe('Test Envelope');
|
||||||
|
expect(envelope?.type).toBe(EnvelopeType.TEMPLATE);
|
||||||
|
expect(envelope?.status).toBe(DocumentStatus.DRAFT);
|
||||||
|
expect(envelope?.envelopeItems.length).toBe(1);
|
||||||
|
expect(envelope?.envelopeItems[0].title).toBe('field-font-alignment.pdf');
|
||||||
|
expect(envelope?.envelopeItems[0].documentDataId).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should create envelope with multiple file', async ({ request }) => {
|
||||||
|
const folder = await prisma.folder.create({
|
||||||
|
data: {
|
||||||
|
name: 'Test Folder',
|
||||||
|
teamId: teamA.id,
|
||||||
|
userId: userA.id,
|
||||||
|
type: FolderType.DOCUMENT,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: 'Envelope Title',
|
||||||
|
type: EnvelopeType.DOCUMENT,
|
||||||
|
externalId: 'externalId',
|
||||||
|
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
globalAccessAuth: ['ACCOUNT'],
|
||||||
|
formValues: {
|
||||||
|
hello: 'world',
|
||||||
|
},
|
||||||
|
folderId: folder.id,
|
||||||
|
recipients: [
|
||||||
|
{
|
||||||
|
email: userA.email,
|
||||||
|
name: 'Name',
|
||||||
|
role: RecipientRole.SIGNER,
|
||||||
|
accessAuth: ['TWO_FACTOR_AUTH'],
|
||||||
|
signingOrder: 1,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: FieldType.SIGNATURE,
|
||||||
|
identifier: 'field-font-alignment.pdf',
|
||||||
|
page: 1,
|
||||||
|
positionX: 0,
|
||||||
|
positionY: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: FieldType.SIGNATURE,
|
||||||
|
identifier: 0,
|
||||||
|
page: 1,
|
||||||
|
positionX: 0,
|
||||||
|
positionY: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
subject: 'Subject',
|
||||||
|
message: 'Message',
|
||||||
|
timezone: 'Europe/Berlin',
|
||||||
|
dateFormat: 'dd.MM.yyyy',
|
||||||
|
distributionMethod: DocumentDistributionMethod.NONE,
|
||||||
|
signingOrder: DocumentSigningOrder.SEQUENTIAL,
|
||||||
|
allowDictateNextSigner: true,
|
||||||
|
redirectUrl: 'https://documenso.com',
|
||||||
|
language: 'de',
|
||||||
|
typedSignatureEnabled: true,
|
||||||
|
uploadSignatureEnabled: false,
|
||||||
|
drawSignatureEnabled: false,
|
||||||
|
emailReplyTo: userA.email,
|
||||||
|
emailSettings: {
|
||||||
|
recipientSigningRequest: false,
|
||||||
|
recipientRemoved: false,
|
||||||
|
recipientSigned: false,
|
||||||
|
documentPending: false,
|
||||||
|
documentCompleted: false,
|
||||||
|
documentDeleted: false,
|
||||||
|
ownerDocumentCompleted: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
label: 'Test Attachment',
|
||||||
|
data: 'https://documenso.com',
|
||||||
|
type: 'link',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} satisfies TCreateEnvelopePayload;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const files = [
|
||||||
|
{
|
||||||
|
name: 'field-meta.pdf',
|
||||||
|
data: fs.readFileSync(path.join(__dirname, '../../../../../assets/field-meta.pdf')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'field-font-alignment.pdf',
|
||||||
|
data: fs.readFileSync(
|
||||||
|
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
formData.append('files', new File([file.data], file.name, { type: 'application/pdf' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should error since folder is not owned by the user.
|
||||||
|
const invalidRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenB}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(invalidRes.ok()).toBeFalsy();
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
|
||||||
|
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||||
|
|
||||||
|
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: response.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
documentMeta: true,
|
||||||
|
envelopeItems: true,
|
||||||
|
recipients: true,
|
||||||
|
fields: true,
|
||||||
|
envelopeAttachments: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(userB.email);
|
||||||
|
|
||||||
|
expect(envelope.envelopeItems.length).toBe(2);
|
||||||
|
expect(envelope.envelopeItems[0].title).toBe('field-meta.pdf');
|
||||||
|
expect(envelope.envelopeItems[1].title).toBe('field-font-alignment.pdf');
|
||||||
|
|
||||||
|
expect(envelope.title).toBe(payload.title);
|
||||||
|
expect(envelope.type).toBe(payload.type);
|
||||||
|
expect(envelope.externalId).toBe(payload.externalId);
|
||||||
|
expect(envelope.visibility).toBe(payload.visibility);
|
||||||
|
expect(envelope.authOptions).toEqual({
|
||||||
|
globalAccessAuth: payload.globalAccessAuth,
|
||||||
|
globalActionAuth: [],
|
||||||
|
});
|
||||||
|
expect(envelope.formValues).toEqual(payload.formValues);
|
||||||
|
expect(envelope.folderId).toBe(payload.folderId);
|
||||||
|
|
||||||
|
expect(envelope.documentMeta.subject).toBe(payload.meta.subject);
|
||||||
|
expect(envelope.documentMeta.message).toBe(payload.meta.message);
|
||||||
|
expect(envelope.documentMeta.timezone).toBe(payload.meta.timezone);
|
||||||
|
expect(envelope.documentMeta.dateFormat).toBe(payload.meta.dateFormat);
|
||||||
|
expect(envelope.documentMeta.distributionMethod).toBe(payload.meta.distributionMethod);
|
||||||
|
expect(envelope.documentMeta.signingOrder).toBe(payload.meta.signingOrder);
|
||||||
|
expect(envelope.documentMeta.allowDictateNextSigner).toBe(
|
||||||
|
payload.meta.allowDictateNextSigner,
|
||||||
|
);
|
||||||
|
expect(envelope.documentMeta.redirectUrl).toBe(payload.meta.redirectUrl);
|
||||||
|
expect(envelope.documentMeta.language).toBe(payload.meta.language);
|
||||||
|
expect(envelope.documentMeta.typedSignatureEnabled).toBe(payload.meta.typedSignatureEnabled);
|
||||||
|
expect(envelope.documentMeta.uploadSignatureEnabled).toBe(
|
||||||
|
payload.meta.uploadSignatureEnabled,
|
||||||
|
);
|
||||||
|
expect(envelope.documentMeta.drawSignatureEnabled).toBe(payload.meta.drawSignatureEnabled);
|
||||||
|
expect(envelope.documentMeta.emailReplyTo).toBe(payload.meta.emailReplyTo);
|
||||||
|
expect(envelope.documentMeta.emailSettings).toEqual(payload.meta.emailSettings);
|
||||||
|
|
||||||
|
expect([
|
||||||
|
{
|
||||||
|
label: envelope.envelopeAttachments[0].label,
|
||||||
|
data: envelope.envelopeAttachments[0].data,
|
||||||
|
type: envelope.envelopeAttachments[0].type,
|
||||||
|
},
|
||||||
|
]).toEqual(payload.attachments);
|
||||||
|
|
||||||
|
const field = envelope.fields[0];
|
||||||
|
const recipient = envelope.recipients[0];
|
||||||
|
|
||||||
|
expect({
|
||||||
|
email: recipient.email,
|
||||||
|
name: recipient.name,
|
||||||
|
role: recipient.role,
|
||||||
|
signingOrder: recipient.signingOrder,
|
||||||
|
accessAuth: recipient.authOptions?.accessAuth,
|
||||||
|
}).toEqual(
|
||||||
|
pick(payload.recipients[0], ['email', 'name', 'role', 'signingOrder', 'accessAuth']),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect({
|
||||||
|
type: field.type,
|
||||||
|
page: field.page,
|
||||||
|
positionX: field.positionX.toNumber(),
|
||||||
|
positionY: field.positionY.toNumber(),
|
||||||
|
width: field.width.toNumber(),
|
||||||
|
height: field.height.toNumber(),
|
||||||
|
}).toEqual(
|
||||||
|
pick(payload.recipients[0].fields[0], [
|
||||||
|
'type',
|
||||||
|
'page',
|
||||||
|
'positionX',
|
||||||
|
'positionY',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expect string based ID to work.
|
||||||
|
expect(field.envelopeItemId).toBe(
|
||||||
|
envelope.envelopeItems.find((item) => item.title === 'field-font-alignment.pdf')?.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Expect index based ID to work.
|
||||||
|
expect(envelope.fields[1].envelopeItemId).toBe(
|
||||||
|
envelope.envelopeItems.find((item) => item.title === 'field-meta.pdf')?.id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates envelopes with the two field test PDFs.
|
* Creates envelopes with the two field test PDFs.
|
||||||
*/
|
*/
|
||||||
test('Envelope full test', async ({ request }) => {
|
test('Envelope full test', async ({ request }) => {
|
||||||
// Step 1: Create initial envelope with Prisma (with first envelope item)
|
// Step 1: Create initial envelope with Prisma (with first envelope item)
|
||||||
const alignmentPdf = fs
|
const alignmentPdf = fs.readFileSync(
|
||||||
.readFileSync(path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'))
|
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
|
||||||
.toString('base64');
|
);
|
||||||
|
|
||||||
const fieldMetaPdf = fs
|
const fieldMetaPdf = fs.readFileSync(
|
||||||
.readFileSync(path.join(__dirname, '../../../../../assets/field-meta.pdf'))
|
path.join(__dirname, '../../../../../assets/field-meta.pdf'),
|
||||||
.toString('base64');
|
);
|
||||||
|
|
||||||
const alignmentDocumentData = await prisma.documentData.create({
|
const formData = new FormData();
|
||||||
data: {
|
|
||||||
type: 'BYTES_64',
|
|
||||||
data: alignmentPdf,
|
|
||||||
initialData: alignmentPdf,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const documentId = await incrementDocumentId();
|
formData.append(
|
||||||
const documentMeta = await prisma.documentMeta.create({
|
'payload',
|
||||||
data: {},
|
JSON.stringify({
|
||||||
});
|
|
||||||
|
|
||||||
const createdEnvelope = await prisma.envelope.create({
|
|
||||||
data: {
|
|
||||||
id: prefixedId('envelope'),
|
|
||||||
secondaryId: documentId.formattedDocumentId,
|
|
||||||
internalVersion: 2,
|
|
||||||
type: EnvelopeType.DOCUMENT,
|
type: EnvelopeType.DOCUMENT,
|
||||||
documentMetaId: documentMeta.id,
|
title: 'Envelope Full Field Test',
|
||||||
source: DocumentSource.DOCUMENT,
|
} satisfies TCreateEnvelopePayload),
|
||||||
title: `Envelope Full Field Test`,
|
);
|
||||||
status: 'DRAFT',
|
|
||||||
userId: userA.id,
|
// Only add one file for now.
|
||||||
teamId: teamA.id,
|
formData.append(
|
||||||
envelopeItems: {
|
'files',
|
||||||
create: {
|
new File([alignmentPdf], 'field-font-alignment.pdf', { type: 'application/pdf' }),
|
||||||
id: prefixedId('envelope_item'),
|
);
|
||||||
title: `Alignment Test`,
|
|
||||||
documentDataId: alignmentDocumentData.id,
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/create`, {
|
||||||
order: 1,
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
},
|
multipart: formData,
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
envelopeItems: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const response: TCreateEnvelopeResponse = await res.json();
|
||||||
|
|
||||||
|
const createdEnvelope: TGetEnvelopeResponse = await request
|
||||||
|
.get(`${baseUrl}/envelope/${response.id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
|
})
|
||||||
|
.then(async (res) => await res.json());
|
||||||
|
|
||||||
|
// Might as well testing access control here as well.
|
||||||
|
const unauthRequest = await request.get(`${baseUrl}/envelope/${response.id}`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenB}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unauthRequest.ok()).toBeFalsy();
|
||||||
|
expect(unauthRequest.status()).toBe(404);
|
||||||
|
|
||||||
// Step 2: Create second envelope item via API
|
// Step 2: Create second envelope item via API
|
||||||
|
// Todo: Envelopes - Use API Route
|
||||||
const fieldMetaDocumentData = await prisma.documentData.create({
|
const fieldMetaDocumentData = await prisma.documentData.create({
|
||||||
data: {
|
data: {
|
||||||
type: 'BYTES_64',
|
type: 'BYTES_64',
|
||||||
data: fieldMetaPdf,
|
data: fieldMetaPdf.toString('base64'),
|
||||||
initialData: fieldMetaPdf,
|
initialData: fieldMetaPdf.toString('base64'),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,7 +430,6 @@ test.describe('API V2 Envelopes', () => {
|
|||||||
// Step 3: Update envelope via API
|
// Step 3: Update envelope via API
|
||||||
const updateEnvelopeRequest: TUpdateEnvelopeRequest = {
|
const updateEnvelopeRequest: TUpdateEnvelopeRequest = {
|
||||||
envelopeId: createdEnvelope.id,
|
envelopeId: createdEnvelope.id,
|
||||||
envelopeType: EnvelopeType.DOCUMENT,
|
|
||||||
data: {
|
data: {
|
||||||
title: 'Envelope Full Field Test',
|
title: 'Envelope Full Field Test',
|
||||||
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
visibility: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import type { Team, User } from '@prisma/client';
|
import type { Team, User } from '@prisma/client';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||||
@ -2858,6 +2860,86 @@ test.describe('Document API V2', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('Document create endpoint', () => {
|
||||||
|
test('should allow authorized access to document create endpoint', async ({ request }) => {
|
||||||
|
const payload = {
|
||||||
|
title: 'Test Document',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const pdfPath = path.join(__dirname, '../../../../../assets/example.pdf');
|
||||||
|
const pdfData = fs.existsSync(pdfPath) ? fs.readFileSync(pdfPath) : Buffer.from('%PDF-1.4\n');
|
||||||
|
|
||||||
|
formData.append('file', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/document/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
expect(data.envelopeId).toBeDefined();
|
||||||
|
expect(data.id).toBeDefined();
|
||||||
|
|
||||||
|
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: data.envelopeId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
envelopeItems: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(envelope?.title).toBe(payload.title);
|
||||||
|
expect(envelope.envelopeItems.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Template create endpoint', () => {
|
||||||
|
test('should allow authorized access to template create endpoint', async ({ request }) => {
|
||||||
|
const payload = {
|
||||||
|
title: 'Test Template',
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('payload', JSON.stringify(payload));
|
||||||
|
|
||||||
|
const pdfPath = path.join(__dirname, '../../../../../assets/example.pdf');
|
||||||
|
const pdfData = fs.existsSync(pdfPath) ? fs.readFileSync(pdfPath) : Buffer.from('%PDF-1.4\n');
|
||||||
|
|
||||||
|
formData.append('file', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
|
||||||
|
|
||||||
|
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/create`, {
|
||||||
|
headers: { Authorization: `Bearer ${tokenA}` },
|
||||||
|
multipart: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.ok()).toBeTruthy();
|
||||||
|
expect(res.status()).toBe(200);
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
expect(data.envelopeId).toBeDefined();
|
||||||
|
expect(data.id).toBeDefined();
|
||||||
|
|
||||||
|
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||||
|
where: {
|
||||||
|
id: data.envelopeId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
envelopeItems: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(envelope.title).toBe(payload.title);
|
||||||
|
expect(envelope.envelopeItems.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test.describe('Envelope API V2', () => {
|
test.describe('Envelope API V2', () => {
|
||||||
let userA: User, teamA: Team, userB: User, teamB: Team, tokenA: string, tokenB: string;
|
let userA: User, teamA: Team, userB: User, teamB: Team, tokenA: string, tokenB: string;
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,6 @@ export type CreateEnvelopeOptions = {
|
|||||||
envelopeItems: { title?: string; documentDataId: string; order?: number }[];
|
envelopeItems: { title?: string; documentDataId: string; order?: number }[];
|
||||||
formValues?: TDocumentFormValues;
|
formValues?: TDocumentFormValues;
|
||||||
|
|
||||||
timezone?: string;
|
|
||||||
userTimezone?: string;
|
userTimezone?: string;
|
||||||
|
|
||||||
templateType?: TemplateType;
|
templateType?: TemplateType;
|
||||||
@ -107,7 +106,6 @@ export const createEnvelope = async ({
|
|||||||
title,
|
title,
|
||||||
externalId,
|
externalId,
|
||||||
formValues,
|
formValues,
|
||||||
timezone,
|
|
||||||
userTimezone,
|
userTimezone,
|
||||||
folderId,
|
folderId,
|
||||||
templateType,
|
templateType,
|
||||||
@ -166,6 +164,7 @@ export const createEnvelope = async ({
|
|||||||
let envelopeItems: { title?: string; documentDataId: string; order?: number }[] =
|
let envelopeItems: { title?: string; documentDataId: string; order?: number }[] =
|
||||||
data.envelopeItems;
|
data.envelopeItems;
|
||||||
|
|
||||||
|
// Todo: Envelopes - Remove
|
||||||
if (normalizePdf) {
|
if (normalizePdf) {
|
||||||
envelopeItems = await Promise.all(
|
envelopeItems = await Promise.all(
|
||||||
data.envelopeItems.map(async (item) => {
|
data.envelopeItems.map(async (item) => {
|
||||||
@ -243,7 +242,7 @@ export const createEnvelope = async ({
|
|||||||
|
|
||||||
// userTimezone is last because it's always passed in regardless of the organisation/team settings
|
// userTimezone is last because it's always passed in regardless of the organisation/team settings
|
||||||
// for uploads from the frontend
|
// for uploads from the frontend
|
||||||
const timezoneToUse = timezone || settings.documentTimezone || userTimezone;
|
const timezoneToUse = meta?.timezone || settings.documentTimezone || userTimezone;
|
||||||
|
|
||||||
const documentMeta = await prisma.documentMeta.create({
|
const documentMeta = await prisma.documentMeta.create({
|
||||||
data: extractDerivedDocumentMeta(settings, {
|
data: extractDerivedDocumentMeta(settings, {
|
||||||
|
|||||||
@ -10,10 +10,12 @@ import { authenticatedProcedure } from '../trpc';
|
|||||||
import {
|
import {
|
||||||
ZCreateDocumentRequestSchema,
|
ZCreateDocumentRequestSchema,
|
||||||
ZCreateDocumentResponseSchema,
|
ZCreateDocumentResponseSchema,
|
||||||
|
createDocumentMeta,
|
||||||
} from './create-document.types';
|
} from './create-document.types';
|
||||||
|
|
||||||
export const createDocumentRoute = authenticatedProcedure
|
export const createDocumentRoute = authenticatedProcedure
|
||||||
.input(ZCreateDocumentRequestSchema) // Note: Before releasing this to public, update the response schema to be correct.
|
.meta(createDocumentMeta)
|
||||||
|
.input(ZCreateDocumentRequestSchema)
|
||||||
.output(ZCreateDocumentResponseSchema)
|
.output(ZCreateDocumentResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { user, teamId } = ctx;
|
const { user, teamId } = ctx;
|
||||||
@ -62,6 +64,6 @@ export const createDocumentRoute = authenticatedProcedure
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
envelopeId: document.id,
|
envelopeId: document.id,
|
||||||
legacyDocumentId: mapSecondaryIdToDocumentId(document.secondaryId),
|
id: mapSecondaryIdToDocumentId(document.secondaryId),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { zodFormData } from '../../utils/zod-form-data';
|
|||||||
import type { TrpcRouteMeta } from '../trpc';
|
import type { TrpcRouteMeta } from '../trpc';
|
||||||
import { ZDocumentTitleSchema } from './schema';
|
import { ZDocumentTitleSchema } from './schema';
|
||||||
|
|
||||||
// Currently not in use until we allow passthrough documents on create.
|
|
||||||
export const createDocumentMeta: TrpcRouteMeta = {
|
export const createDocumentMeta: TrpcRouteMeta = {
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -42,7 +41,7 @@ export const ZCreateDocumentRequestSchema = zodFormData({
|
|||||||
|
|
||||||
export const ZCreateDocumentResponseSchema = z.object({
|
export const ZCreateDocumentResponseSchema = z.object({
|
||||||
envelopeId: z.string(),
|
envelopeId: z.string(),
|
||||||
legacyDocumentId: z.number(),
|
id: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCreateDocumentPayloadSchema = z.infer<typeof ZCreateDocumentPayloadSchema>;
|
export type TCreateDocumentPayloadSchema = z.infer<typeof ZCreateDocumentPayloadSchema>;
|
||||||
|
|||||||
@ -10,15 +10,14 @@ import {
|
|||||||
} from './create-envelope.types';
|
} from './create-envelope.types';
|
||||||
|
|
||||||
export const createEnvelopeRoute = authenticatedProcedure
|
export const createEnvelopeRoute = authenticatedProcedure
|
||||||
// Todo: Envelopes - Pending direct uploads
|
.meta({
|
||||||
// .meta({
|
openapi: {
|
||||||
// openapi: {
|
method: 'POST',
|
||||||
// method: 'POST',
|
path: '/envelope/create',
|
||||||
// path: '/envelope/create',
|
summary: 'Create envelope',
|
||||||
// summary: 'Create envelope',
|
tags: ['Envelope'],
|
||||||
// tags: ['Envelope'],
|
},
|
||||||
// },
|
})
|
||||||
// })
|
|
||||||
.input(ZCreateEnvelopeRequestSchema)
|
.input(ZCreateEnvelopeRequestSchema)
|
||||||
.output(ZCreateEnvelopeResponseSchema)
|
.output(ZCreateEnvelopeResponseSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
@ -33,6 +32,7 @@ export const createEnvelopeRoute = authenticatedProcedure
|
|||||||
visibility,
|
visibility,
|
||||||
globalAccessAuth,
|
globalAccessAuth,
|
||||||
globalActionAuth,
|
globalActionAuth,
|
||||||
|
formValues,
|
||||||
recipients,
|
recipients,
|
||||||
folderId,
|
folderId,
|
||||||
meta,
|
meta,
|
||||||
@ -121,6 +121,7 @@ export const createEnvelopeRoute = authenticatedProcedure
|
|||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
externalId,
|
externalId,
|
||||||
|
formValues,
|
||||||
visibility,
|
visibility,
|
||||||
globalAccessAuth,
|
globalAccessAuth,
|
||||||
globalActionAuth,
|
globalActionAuth,
|
||||||
@ -130,7 +131,6 @@ export const createEnvelopeRoute = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
attachments,
|
attachments,
|
||||||
meta,
|
meta,
|
||||||
normalizePdf: true,
|
|
||||||
requestMetadata: ctx.metadata,
|
requestMetadata: ctx.metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -10,11 +10,11 @@ import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-va
|
|||||||
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
import { ZDocumentMetaCreateSchema } from '@documenso/lib/types/document-meta';
|
||||||
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
import { ZEnvelopeAttachmentTypeSchema } from '@documenso/lib/types/envelope-attachment';
|
||||||
import {
|
import {
|
||||||
ZFieldHeightSchema,
|
ZClampedFieldHeightSchema,
|
||||||
|
ZClampedFieldPositionXSchema,
|
||||||
|
ZClampedFieldPositionYSchema,
|
||||||
|
ZClampedFieldWidthSchema,
|
||||||
ZFieldPageNumberSchema,
|
ZFieldPageNumberSchema,
|
||||||
ZFieldPageXSchema,
|
|
||||||
ZFieldPageYSchema,
|
|
||||||
ZFieldWidthSchema,
|
|
||||||
} from '@documenso/lib/types/field';
|
} from '@documenso/lib/types/field';
|
||||||
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
import { ZFieldAndMetaSchema } from '@documenso/lib/types/field-meta';
|
||||||
|
|
||||||
@ -65,10 +65,10 @@ export const ZCreateEnvelopePayloadSchema = z.object({
|
|||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
page: ZFieldPageNumberSchema,
|
page: ZFieldPageNumberSchema,
|
||||||
positionX: ZFieldPageXSchema,
|
positionX: ZClampedFieldPositionXSchema,
|
||||||
positionY: ZFieldPageYSchema,
|
positionY: ZClampedFieldPositionYSchema,
|
||||||
width: ZFieldWidthSchema,
|
width: ZClampedFieldWidthSchema,
|
||||||
height: ZFieldHeightSchema,
|
height: ZClampedFieldHeightSchema,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.array()
|
.array()
|
||||||
|
|||||||
@ -28,8 +28,8 @@ export const ZCreateRecipientSchema = z.object({
|
|||||||
name: z.string().max(255),
|
name: z.string().max(255),
|
||||||
role: z.nativeEnum(RecipientRole),
|
role: z.nativeEnum(RecipientRole),
|
||||||
signingOrder: z.number().optional(),
|
signingOrder: z.number().optional(),
|
||||||
accessAuth: z.array(ZRecipientAccessAuthTypesSchema).optional().default([]),
|
accessAuth: z.array(ZRecipientAccessAuthTypesSchema).default([]).optional(),
|
||||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
actionAuth: z.array(ZRecipientActionAuthTypesSchema).default([]).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZUpdateRecipientSchema = z.object({
|
export const ZUpdateRecipientSchema = z.object({
|
||||||
@ -38,8 +38,8 @@ export const ZUpdateRecipientSchema = z.object({
|
|||||||
name: z.string().max(255).optional(),
|
name: z.string().max(255).optional(),
|
||||||
role: z.nativeEnum(RecipientRole).optional(),
|
role: z.nativeEnum(RecipientRole).optional(),
|
||||||
signingOrder: z.number().optional(),
|
signingOrder: z.number().optional(),
|
||||||
accessAuth: z.array(ZRecipientAccessAuthTypesSchema).optional().default([]),
|
accessAuth: z.array(ZRecipientAccessAuthTypesSchema).default([]).optional(),
|
||||||
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
|
actionAuth: z.array(ZRecipientActionAuthTypesSchema).default([]).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZCreateDocumentRecipientRequestSchema = z.object({
|
export const ZCreateDocumentRecipientRequestSchema = z.object({
|
||||||
|
|||||||
@ -161,7 +161,6 @@ export const templateRouter = router({
|
|||||||
*/
|
*/
|
||||||
createTemplate: authenticatedProcedure
|
createTemplate: authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
// Note before releasing this to public, update the response schema to be correct.
|
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/template/create',
|
path: '/template/create',
|
||||||
@ -207,7 +206,7 @@ export const templateRouter = router({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
envelopeId: envelope.id,
|
envelopeId: envelope.id,
|
||||||
legacyTemplateId: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
id: mapSecondaryIdToTemplateId(envelope.secondaryId),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@ -231,7 +231,7 @@ export const ZCreateTemplateV2ResponseSchema = z.object({
|
|||||||
|
|
||||||
export const ZCreateTemplateResponseSchema = z.object({
|
export const ZCreateTemplateResponseSchema = z.object({
|
||||||
envelopeId: z.string(),
|
envelopeId: z.string(),
|
||||||
legacyTemplateId: z.number(),
|
id: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ZUpdateTemplateRequestSchema = z.object({
|
export const ZUpdateTemplateRequestSchema = z.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user