fix: lint project (#2693)

This commit is contained in:
David Nguyen
2026-05-08 16:04:22 +10:00
committed by GitHub
parent edbf65969b
commit 8671f269e8
1414 changed files with 12867 additions and 24335 deletions
@@ -1,19 +1,14 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { generateDatabaseId } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
import { DocumentStatus } from '@documenso/prisma/client';
import {
seedCompletedDocument,
seedDocuments,
seedPendingDocument,
} from '@documenso/prisma/seed/documents';
import { seedCompletedDocument, seedDocuments, seedPendingDocument } from '@documenso/prisma/seed/documents';
import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { apiSignin, apiSignout } from '../../fixtures/authentication';
@@ -1,7 +1,3 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { generateDatabaseId } from '@documenso/lib/universal/id';
import { prisma } from '@documenso/prisma';
@@ -9,6 +5,9 @@ import { seedOrganisationMembers } from '@documenso/prisma/seed/organisations';
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
import { apiSignin, apiSignout } from '../../fixtures/authentication';
@@ -171,9 +170,7 @@ test.describe('Template Search - Cross-Team Isolation', () => {
// ─── Recipient Email Search ──────────────────────────────────────────────────
test.describe('Template Search - Recipient Email', () => {
test('should find templates by recipient email within team but not cross-team', async ({
page,
}) => {
test('should find templates by recipient email within team but not cross-team', async ({ page }) => {
const { team: teamA, owner: ownerA } = await seedTeam();
const adminUserA = await seedTeamMember({ teamId: teamA.id, role: TeamMemberRole.ADMIN });
const { team: teamB, owner: ownerB } = await seedTeam();
@@ -1,15 +1,11 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { FieldType, RecipientRole } from '@documenso/prisma/client';
import {
seedBlankDocument,
seedPendingDocumentWithFullFields,
} from '@documenso/prisma/seed/documents';
import { seedBlankDocument, seedPendingDocumentWithFullFields } from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
test.describe('Document API', () => {
test('sendDocument: should respect sendCompletionEmails setting', async ({ request }) => {
@@ -193,9 +189,7 @@ test.describe('Document API', () => {
expect(response.status()).toBe(400);
});
test('sendDocument: should fail when signer has only non-signature fields', async ({
request,
}) => {
test('sendDocument: should fail when signer has only non-signature fields', async ({ request }) => {
const { user, team } = await seedUser();
// Create a blank document and get it with envelope items
@@ -1,5 +1,3 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import type { TCheckboxFieldMeta, TRadioFieldMeta } from '@documenso/lib/types/field-meta';
@@ -12,16 +10,14 @@ import { prisma } from '@documenso/prisma';
import { FieldType, RecipientRole } from '@documenso/prisma/client';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import { apiSignin } from '../../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
test.describe('Template Field Prefill API v1', () => {
test('should create a document from template with prefilled fields', async ({
page,
request,
}) => {
test('should create a document from template with prefilled fields', async ({ page, request }) => {
// 1. Create a user
const { user, team } = await seedUser();
@@ -196,9 +192,7 @@ test.describe('Template Field Prefill API v1', () => {
});
// 7. Navigate to the template
await page.goto(
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
);
await page.goto(`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`);
// 8. Create a document from the template with prefilled fields
const response = await request.post(
@@ -381,10 +375,7 @@ test.describe('Template Field Prefill API v1', () => {
await expect(page.getByText('Select B')).toBeVisible();
});
test('should create a document from template without prefilled fields', async ({
page,
request,
}) => {
test('should create a document from template without prefilled fields', async ({ page, request }) => {
// 1. Create a user
const { user, team } = await seedUser();
@@ -487,9 +478,7 @@ test.describe('Template Field Prefill API v1', () => {
});
// 7. Navigate to the template
await page.goto(
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
);
await page.goto(`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`);
// 8. Create a document from the template without prefilled fields
const response = await request.post(
@@ -1,11 +1,6 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import {
mapSecondaryIdToDocumentId,
mapSecondaryIdToTemplateId,
} from '@documenso/lib/utils/envelope';
import { mapSecondaryIdToDocumentId, mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { DocumentDataType, FieldType } from '@documenso/prisma/client';
import {
@@ -16,6 +11,7 @@ import {
} from '@documenso/prisma/seed/documents';
import { seedBlankTemplate, seedTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
@@ -25,9 +21,7 @@ test.describe.configure({
test.describe('Document Access API V1', () => {
test.describe('Document GET endpoint', () => {
test('should block unauthorized access to documents not owned by the user', async ({
request,
}) => {
test('should block unauthorized access to documents not owned by the user', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -319,9 +313,7 @@ test.describe('Document Access API V1', () => {
});
test.describe('Document recipients POST endpoint', () => {
test('should block unauthorized access to document recipients endpoint', async ({
request,
}) => {
test('should block unauthorized access to document recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -371,9 +363,7 @@ test.describe('Document Access API V1', () => {
});
test.describe('Document recipients PATCH endpoint', () => {
test('should block unauthorized access to PATCH on recipients endpoint', async ({
request,
}) => {
test('should block unauthorized access to PATCH on recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -459,9 +449,7 @@ test.describe('Document Access API V1', () => {
});
test.describe('Document recipients DELETE endpoint', () => {
test('should block unauthorized access to DELETE on recipients endpoint', async ({
request,
}) => {
test('should block unauthorized access to DELETE on recipients endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -972,9 +960,7 @@ test.describe('Document Access API V1', () => {
expect(resB.ok()).toBeTruthy();
expect(resB.status()).toBe(200);
expect(reqData.documents.every((doc: { userId: number }) => doc.userId !== userA.id)).toBe(
true,
);
expect(reqData.documents.every((doc: { userId: number }) => doc.userId !== userA.id)).toBe(true);
expect(reqData.documents.length).toBe(0);
expect(reqData.totalPages).toBe(0);
});
@@ -999,9 +985,7 @@ test.describe('Document Access API V1', () => {
expect(resA.ok()).toBeTruthy();
expect(resA.status()).toBe(200);
expect(reqData.documents.length).toBeGreaterThan(0);
expect(reqData.documents.every((doc: { userId: number }) => doc.userId === userA.id)).toBe(
true,
);
expect(reqData.documents.every((doc: { userId: number }) => doc.userId === userA.id)).toBe(true);
});
});
@@ -1027,9 +1011,7 @@ test.describe('Document Access API V1', () => {
expect(resB.ok()).toBeTruthy();
expect(resB.status()).toBe(200);
expect(reqData.templates.every((tpl: { userId: number }) => tpl.userId !== userA.id)).toBe(
true,
);
expect(reqData.templates.every((tpl: { userId: number }) => tpl.userId !== userA.id)).toBe(true);
expect(reqData.templates.length).toBe(0);
expect(reqData.totalPages).toBe(0);
});
@@ -1054,16 +1036,12 @@ test.describe('Document Access API V1', () => {
expect(resA.ok()).toBeTruthy();
expect(resA.status()).toBe(200);
expect(reqData.templates.length).toBeGreaterThan(0);
expect(reqData.templates.every((tpl: { userId: number }) => tpl.userId === userA.id)).toBe(
true,
);
expect(reqData.templates.every((tpl: { userId: number }) => tpl.userId === userA.id)).toBe(true);
});
});
test.describe('Create document from template endpoint', () => {
test('should block unauthorized access to create-document-from-template endpoint', async ({
request,
}) => {
test('should block unauthorized access to create-document-from-template endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -1104,9 +1082,7 @@ test.describe('Document Access API V1', () => {
expect(resB.status()).toBe(401);
});
test('should allow authorized access to create-document-from-template endpoint', async ({
request,
}) => {
test('should allow authorized access to create-document-from-template endpoint', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { token: tokenA } = await createApiToken({
userId: userA.id,
@@ -1,8 +1,5 @@
import { type APIRequestContext, expect, test } from '@playwright/test';
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 { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { EnvelopeType, FieldType, RecipientRole } from '@documenso/prisma/client';
@@ -13,6 +10,8 @@ import type {
} 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 { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
import { type APIRequestContext, expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
@@ -138,9 +137,7 @@ test.describe('Envelope distribute validation', () => {
expect(errorResponse.message).toContain('Signers must have at least one signature field');
});
test('should fail to distribute when signer has non-signature fields only', async ({
request,
}) => {
test('should fail to distribute when signer has non-signature fields only', async ({ request }) => {
const envelope = await createEnvelope(request, token);
const envelopeData = await getEnvelope(request, token, envelope.id);
@@ -321,9 +318,7 @@ test.describe('Envelope distribute validation', () => {
expect(distributeRes.status()).toBe(200);
});
test('should fail when one of multiple signers is missing signature field', async ({
request,
}) => {
test('should fail when one of multiple signers is missing signature field', async ({ request }) => {
const envelope = await createEnvelope(request, token);
const envelopeData = await getEnvelope(request, token, envelope.id);
@@ -1,16 +1,13 @@
import { expect, test } from '@playwright/test';
import type { APIRequestContext } from 'playwright-core';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import type { CreateEmbeddingPresignTokenOptions } from '@documenso/lib/server-only/embedding-presign/create-embedding-presign-token';
import type { VerifyEmbeddingPresignTokenOptions } from '@documenso/lib/server-only/embedding-presign/verify-embedding-presign-token';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import type { APIRequestContext } from 'playwright-core';
test.describe('Embedding Presign API', () => {
test('createEmbeddingPresignToken: should create a token with default expiration', async ({
request,
}) => {
test('createEmbeddingPresignToken: should create a token with default expiration', async ({ request }) => {
const { user, team } = await seedUser();
const { token } = await createApiToken({
@@ -34,9 +31,7 @@ test.describe('Embedding Presign API', () => {
expect(responseData.expiresIn).toBe(3600); // Default 1 hour in seconds
});
test('createEmbeddingPresignToken: should create a token with custom expiration', async ({
request,
}) => {
test('createEmbeddingPresignToken: should create a token with custom expiration', async ({ request }) => {
const { user, team } = await seedUser();
const { token } = await createApiToken({
@@ -187,9 +182,7 @@ test.describe('Embedding Presign API', () => {
expect(verifyResponseData.success).toBe(true);
});
test('verifyEmbeddingPresignToken: should reject a scope mismatched token', async ({
request,
}) => {
test('verifyEmbeddingPresignToken: should reject a scope mismatched token', async ({ request }) => {
const { user, team } = await seedUser();
const { token } = await createApiToken({
@@ -233,19 +226,16 @@ const createPresignToken = async (
apiToken: string,
data?: Partial<CreateEmbeddingPresignTokenOptions>,
) => {
return await request.post(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta/embedding/create-presign-token`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
data: {
apiToken,
...data,
},
return await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta/embedding/create-presign-token`, {
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
);
data: {
apiToken,
...data,
},
});
};
const verifyPresignToken = async (
@@ -253,14 +243,11 @@ const verifyPresignToken = async (
apiToken: string,
data: VerifyEmbeddingPresignTokenOptions,
) => {
return await request.post(
`${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta/embedding/verify-presign-token`,
{
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
data,
return await request.post(`${NEXT_PUBLIC_WEBAPP_URL()}/api/v2-beta/embedding/verify-presign-token`, {
headers: {
Authorization: `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
);
data,
});
};
@@ -1,9 +1,5 @@
import { type APIRequestContext, expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import fs from 'node:fs';
import path from 'node:path';
import { pick } from 'remeda';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { DocumentAccessAuth } from '@documenso/lib/types/document-auth';
@@ -19,17 +15,20 @@ import {
RecipientRole,
} from '@documenso/prisma/client';
import { seedUser } from '@documenso/prisma/seed/users';
import type { TCreateEnvelopeItemsPayload } 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 { TCreateEnvelopeItemsPayload } from '@documenso/trpc/server/envelope-router/create-envelope-items.types';
import type { TDistributeEnvelopeRequest } from '@documenso/trpc/server/envelope-router/distribute-envelope.types';
import type { TCreateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/create-envelope-recipients.types';
import type { TUpdateEnvelopeRecipientsRequest } from '@documenso/trpc/server/envelope-router/envelope-recipients/update-envelope-recipients.types';
import type { TFindEnvelopesResponse } from '@documenso/trpc/server/envelope-router/find-envelopes.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 APIRequestContext, expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { pick } from 'remeda';
import { ALIGNMENT_TEST_FIELDS } from '../../../constants/field-alignment-pdf';
import { FIELD_META_TEST_FIELDS } from '../../../constants/field-meta-pdf';
@@ -94,9 +93,7 @@ test.describe('API V2 Envelopes', () => {
const files = [
{
name: 'field-font-alignment.pdf',
data: fs.readFileSync(
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
),
data: fs.readFileSync(path.join(__dirname, '../../../../../assets/field-font-alignment.pdf')),
},
];
@@ -232,9 +229,7 @@ test.describe('API V2 Envelopes', () => {
},
{
name: 'field-font-alignment.pdf',
data: fs.readFileSync(
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
),
data: fs.readFileSync(path.join(__dirname, '../../../../../assets/field-font-alignment.pdf')),
},
];
@@ -294,15 +289,11 @@ test.describe('API V2 Envelopes', () => {
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.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.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);
@@ -324,9 +315,7 @@ test.describe('API V2 Envelopes', () => {
role: recipient.role,
signingOrder: recipient.signingOrder,
accessAuth: recipient.authOptions?.accessAuth,
}).toEqual(
pick(payload.recipients[0], ['email', 'name', 'role', 'signingOrder', 'accessAuth']),
);
}).toEqual(pick(payload.recipients[0], ['email', 'name', 'role', 'signingOrder', 'accessAuth']));
expect({
type: field.type,
@@ -335,16 +324,7 @@ test.describe('API V2 Envelopes', () => {
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',
]),
);
}).toEqual(pick(payload.recipients[0].fields[0], ['type', 'page', 'positionX', 'positionY', 'width', 'height']));
// Expect string based ID to work.
expect(field.envelopeItemId).toBe(
@@ -363,13 +343,9 @@ test.describe('API V2 Envelopes', () => {
*/
test('Envelope full test', async ({ request }) => {
// Step 1: Create initial envelope with Prisma (with first envelope item)
const alignmentPdf = fs.readFileSync(
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
);
const alignmentPdf = fs.readFileSync(path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'));
const fieldMetaPdf = fs.readFileSync(
path.join(__dirname, '../../../../../assets/field-meta.pdf'),
);
const fieldMetaPdf = fs.readFileSync(path.join(__dirname, '../../../../../assets/field-meta.pdf'));
const formData = new FormData();
@@ -382,10 +358,7 @@ test.describe('API V2 Envelopes', () => {
);
// Only add one file for now.
formData.append(
'files',
new File([alignmentPdf], 'field-font-alignment.pdf', { type: 'application/pdf' }),
);
formData.append('files', new File([alignmentPdf], 'field-font-alignment.pdf', { type: 'application/pdf' }));
const createEnvelopeRequest = await request.post(`${baseUrl}/envelope/create`, {
headers: { Authorization: `Bearer ${tokenA}` },
@@ -418,10 +391,7 @@ test.describe('API V2 Envelopes', () => {
const createEnvelopeItemFormData = new FormData();
createEnvelopeItemFormData.append('payload', JSON.stringify(createEnvelopeItemsPayload));
createEnvelopeItemFormData.append(
'files',
new File([fieldMetaPdf], 'field-meta.pdf', { type: 'application/pdf' }),
);
createEnvelopeItemFormData.append('files', new File([fieldMetaPdf], 'field-meta.pdf', { type: 'application/pdf' }));
const createItemsRes = await request.post(`${baseUrl}/envelope/item/create-many`, {
headers: { Authorization: `Bearer ${tokenA}` },
@@ -481,12 +451,8 @@ test.describe('API V2 Envelopes', () => {
const envelopeResponse = (await getRes.json()) as TGetEnvelopeResponse;
const recipientId = envelopeResponse.recipients[0].id;
const alignmentItem = envelopeResponse.envelopeItems.find(
(item: { order: number }) => item.order === 1,
);
const fieldMetaItem = envelopeResponse.envelopeItems.find(
(item: { order: number }) => item.order === 2,
);
const alignmentItem = envelopeResponse.envelopeItems.find((item: { order: number }) => item.order === 1);
const fieldMetaItem = envelopeResponse.envelopeItems.find((item: { order: number }) => item.order === 2);
expect(recipientId).toBeDefined();
expect(alignmentItem).toBeDefined();
@@ -555,9 +521,7 @@ test.describe('API V2 Envelopes', () => {
// Verify structure
expect(finalEnvelope.envelopeItems.length).toBe(2);
expect(finalEnvelope.recipients.length).toBe(1);
expect(finalEnvelope.fields.length).toBe(
ALIGNMENT_TEST_FIELDS.length + FIELD_META_TEST_FIELDS.length,
);
expect(finalEnvelope.fields.length).toBe(ALIGNMENT_TEST_FIELDS.length + FIELD_META_TEST_FIELDS.length);
expect(finalEnvelope.title).toBe('Envelope Full Field Test');
expect(finalEnvelope.type).toBe(EnvelopeType.DOCUMENT);
@@ -568,17 +532,11 @@ test.describe('API V2 Envelopes', () => {
});
test.describe('Envelope find endpoint', () => {
const createEnvelope = async (
request: APIRequestContext,
token: string,
payload: TCreateEnvelopePayload,
) => {
const createEnvelope = async (request: APIRequestContext, token: string, payload: TCreateEnvelopePayload) => {
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
const pdfData = fs.readFileSync(
path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'),
);
const pdfData = fs.readFileSync(path.join(__dirname, '../../../../../assets/field-font-alignment.pdf'));
formData.append('files', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
const res = await request.post(`${baseUrl}/envelope/create`, {
@@ -1199,9 +1157,7 @@ test.describe('API V2 Envelopes', () => {
expect(distributeResponse.recipients[1].signingUrl).toBeTruthy();
});
test('Distribute envelope with empty email recipient and auth requirements fails', async ({
request,
}) => {
test('Distribute envelope with empty email recipient and auth requirements fails', async ({ request }) => {
const payload = {
type: EnvelopeType.DOCUMENT,
title: 'Document with Auth Requirements',
@@ -1,6 +1,3 @@
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
@@ -15,6 +12,8 @@ import {
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import type { TFindDocumentsResponse } from '@documenso/trpc/server/document-router/find-documents.types';
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { apiSignin } from '../../fixtures/authentication';
@@ -78,9 +77,7 @@ test.describe('Find Documents API - Personal Context', () => {
expect(json!.totalPages).toBe(0);
});
test('should return only documents owned by the user and not the other user', async ({
request,
}) => {
test('should return only documents owned by the user and not the other user', async ({ request }) => {
// The v2 API token scopes to a team. A personal team token only returns
// docs belonging to that team — cross-team received docs are NOT included.
await seedDraftDocument(userA, teamA.id, [], {
@@ -287,9 +284,7 @@ test.describe('Find Documents API - Personal Context', () => {
expect(json!.data[0].title).toBe('Quarterly Report 2024');
});
test('should search by recipient email and not return docs with different recipients', async ({
request,
}) => {
test('should search by recipient email and not return docs with different recipients', async ({ request }) => {
const { user: userC } = await seedUser();
await seedPendingDocument(userA, teamA.id, [userB], {
@@ -404,9 +399,7 @@ test.describe('Find Documents API - Personal Context', () => {
}
});
test('should only show root-level documents when no folderId is provided', async ({
request,
}) => {
test('should only show root-level documents when no folderId is provided', async ({ request }) => {
const folder = await prisma.folder.create({
data: {
name: 'Test Folder',
@@ -499,9 +492,7 @@ test.describe('Find Documents API - Personal Context', () => {
expect(doc.recipients[0].email).toBe(userB.email);
});
test('should not return deleted documents but should return non-deleted ones', async ({
request,
}) => {
test('should not return deleted documents but should return non-deleted ones', async ({ request }) => {
const deletedDoc = await seedDraftDocument(userA, teamA.id, [], {
createDocumentOptions: { title: 'Deleted Document' },
});
@@ -562,9 +553,7 @@ test.describe('Find Documents API - Personal Context', () => {
});
test.describe('Find Documents API - Team Context', () => {
test('should return team documents for team members and exclude non-team docs', async ({
request,
}) => {
test('should return team documents for team members and exclude non-team docs', async ({ request }) => {
const { team, owner } = await seedTeam();
const member = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
@@ -723,9 +712,7 @@ test.describe('Find Documents API - Team Context', () => {
expect(json!.count).toBe(2);
});
test('should enforce visibility across admin and manager levels with adequate data', async ({
request,
}) => {
test('should enforce visibility across admin and manager levels with adequate data', async ({ request }) => {
// Note: MEMBER role cannot create API tokens (requires MANAGE_TEAM permission).
// MEMBER visibility is tested in the UI test file instead.
const { team, owner } = await seedTeam();
@@ -978,9 +965,7 @@ test.describe('Find Documents API - Team with Team Email', () => {
expect(titles).not.toContain('External Noise Doc');
});
test('team email documents should respect visibility rules with adequate controls', async ({
request,
}) => {
test('team email documents should respect visibility rules with adequate controls', async ({ request }) => {
const { team } = await seedTeam();
const teamEmail = `team-vis-email-${team.id}@test.documenso.com`;
@@ -1033,9 +1018,7 @@ test.describe('Find Documents API - Team with Team Email', () => {
});
test.describe('Find Documents API - Deleted Document Handling', () => {
test('should not show soft-deleted documents for owner but show non-deleted ones', async ({
request,
}) => {
test('should not show soft-deleted documents for owner but show non-deleted ones', async ({ request }) => {
const { user, team } = await seedUser();
const deletedDoc = await seedPendingDocument(user, team.id, [], {
@@ -1068,9 +1051,7 @@ test.describe('Find Documents API - Deleted Document Handling', () => {
expect(titles).not.toContain('Soft Deleted by Owner');
});
test('should not show documents where owner soft-deleted their copy in personal context', async ({
request,
}) => {
test('should not show documents where owner soft-deleted their copy in personal context', async ({ request }) => {
// In personal context, documentDeletedAt on recipient hides the doc for that user.
// Note: the v2 API scopes to a team, so we test this by having the owner
// soft-delete a doc from their own personal team.
@@ -1102,9 +1083,7 @@ test.describe('Find Documents API - Deleted Document Handling', () => {
expect(titles).not.toContain('Owner Soft Deleted');
});
test('should not show deleted team documents for any team member but show non-deleted ones', async ({
request,
}) => {
test('should not show deleted team documents for any team member but show non-deleted ones', async ({ request }) => {
const { team, owner } = await seedTeam();
const member = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
@@ -1198,9 +1177,7 @@ test.describe('Find Documents API - Edge Cases', () => {
expect(res.ok()).toBeFalsy();
});
test('personal documents should not appear in team context even with adequate team data', async ({
request,
}) => {
test('personal documents should not appear in team context even with adequate team data', async ({ request }) => {
const { team, owner } = await seedTeam();
const teamMember = await seedTeamMember({
@@ -1276,9 +1253,7 @@ const trpcQuery = async (
};
test.describe('Find Documents API - Adversarial: x-team-id Header Spoofing', () => {
test('should reject request when user spoofs x-team-id to a team they do not belong to', async ({
page,
}) => {
test('should reject request when user spoofs x-team-id to a team they do not belong to', async ({ page }) => {
// Setup: two separate teams with documents
const { team: teamA, owner: ownerA } = await seedTeam();
const { team: teamB, owner: ownerB } = await seedTeam();
@@ -1365,9 +1340,7 @@ test.describe('Find Documents API - Adversarial: x-team-id Header Spoofing', ()
});
test.describe('Find Documents API - Adversarial: Cross-Team folderId', () => {
test('should NOT return documents from another team when folderId belongs to that team', async ({
request,
}) => {
test('should NOT return documents from another team when folderId belongs to that team', async ({ request }) => {
// Setup: two teams each with a folder and documents
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -1452,9 +1425,7 @@ test.describe('Find Documents API - Adversarial: Cross-Team folderId', () => {
});
test.describe('Find Documents API - Adversarial: Cross-Team senderIds', () => {
test('should NOT return documents when senderIds contains users from another team', async ({
page,
}) => {
test('should NOT return documents when senderIds contains users from another team', async ({ page }) => {
const { team: teamA, owner: ownerA } = await seedTeam();
const { team: teamB, owner: ownerB } = await seedTeam();
@@ -1495,9 +1466,7 @@ test.describe('Find Documents API - Adversarial: Cross-Team senderIds', () => {
expect(docs2[0].title).toBe('TeamA Doc by OwnerA');
});
test('senderIds with mix of valid and cross-team userIds should only return matching team docs', async ({
page,
}) => {
test('senderIds with mix of valid and cross-team userIds should only return matching team docs', async ({ page }) => {
const { team, owner } = await seedTeam();
const member = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
const { user: outsider } = await seedUser();
@@ -1542,9 +1511,7 @@ test.describe('Find Documents API - Adversarial: Cross-Team senderIds', () => {
});
test.describe('Find Documents API - Adversarial: Cross-Team templateId', () => {
test('should NOT return documents from another team when filtering by their templateId', async ({
request,
}) => {
test('should NOT return documents from another team when filtering by their templateId', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -1,15 +1,7 @@
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import {
DocumentStatus,
DocumentVisibility,
EnvelopeType,
TeamMemberRole,
} from '@documenso/prisma/client';
import { DocumentStatus, DocumentVisibility, EnvelopeType, TeamMemberRole } from '@documenso/prisma/client';
import {
seedBlankDocument,
seedCompletedDocument,
@@ -19,6 +11,8 @@ import {
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
import type { TFindEnvelopesResponse } from '@documenso/trpc/server/envelope-router/find-envelopes.types';
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { apiSignin } from '../../fixtures/authentication';
@@ -81,9 +75,7 @@ test.describe('Find Envelopes API - Basic', () => {
expect(json!.totalPages).toBe(0);
});
test('should return only envelopes owned by the user and not the other user', async ({
request,
}) => {
test('should return only envelopes owned by the user and not the other user', async ({ request }) => {
await seedDraftDocument(userA, teamA.id, [], {
createDocumentOptions: { title: 'UserA Doc' },
});
@@ -445,9 +437,7 @@ test.describe('Find Envelopes API - Token Masking', () => {
test.describe('Find Envelopes API - Team Context', () => {
// Regression test: findEnvelopes previously had `{ userId }` as a top-level OR branch with no
// teamId constraint, so personal docs leaked into team context. Fixed in the Kysely refactor.
test('should return team envelopes for team members and exclude personal docs', async ({
request,
}) => {
test('should return team envelopes for team members and exclude personal docs', async ({ request }) => {
const { team, owner } = await seedTeam();
const member = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
@@ -672,9 +662,7 @@ test.describe('Find Envelopes API - Team Context', () => {
// ─── Team with Team Email ────────────────────────────────────────────────────
test.describe('Find Envelopes API - Team Email', () => {
test('should include envelopes received by team email from external senders', async ({
request,
}) => {
test('should include envelopes received by team email from external senders', async ({ request }) => {
const { team, owner } = await seedTeam();
const teamEmailAddr = `team-find-env-${team.id}@test.documenso.com`;
await seedTeamEmail({ email: teamEmailAddr, teamId: team.id });
@@ -711,9 +699,7 @@ test.describe('Find Envelopes API - Team Email', () => {
expect(titles).not.toContain('External Noise Doc');
});
test('should NOT include external noise from other teams when team has team email', async ({
request,
}) => {
test('should NOT include external noise from other teams when team has team email', async ({ request }) => {
const { team: teamA, owner: ownerA } = await seedTeam();
const teamEmailAddr = `team-noise-${teamA.id}@test.documenso.com`;
await seedTeamEmail({ email: teamEmailAddr, teamId: teamA.id });
@@ -809,9 +795,7 @@ const trpcQuery = async (
};
test.describe('Find Envelopes API - Adversarial: x-team-id Header Spoofing', () => {
test('should reject request when user spoofs x-team-id to a team they do not belong to', async ({
page,
}) => {
test('should reject request when user spoofs x-team-id to a team they do not belong to', async ({ page }) => {
const { team: teamA, owner: ownerA } = await seedTeam();
const { team: teamB, owner: ownerB } = await seedTeam();
@@ -883,9 +867,7 @@ test.describe('Find Envelopes API - Adversarial: x-team-id Header Spoofing', ()
});
test.describe('Find Envelopes API - Adversarial: Cross-Team folderId', () => {
test('should NOT return envelopes from another team when folderId belongs to that team', async ({
request,
}) => {
test('should NOT return envelopes from another team when folderId belongs to that team', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -965,9 +947,7 @@ test.describe('Find Envelopes API - Adversarial: Cross-Team folderId', () => {
});
test.describe('Find Envelopes API - Adversarial: Cross-Team templateId', () => {
test('should NOT return envelopes from another team when filtering by their templateId', async ({
request,
}) => {
test('should NOT return envelopes from another team when filtering by their templateId', async ({ request }) => {
const { user: userA, team: teamA } = await seedUser();
const { user: userB, team: teamB } = await seedUser();
@@ -1007,9 +987,7 @@ test.describe('Find Envelopes API - Adversarial: Cross-Team templateId', () => {
// ─── Personal vs Team Isolation ──────────────────────────────────────────────
test.describe('Find Envelopes API - Cross-User Isolation', () => {
test('other users personal envelopes should never appear regardless of context', async ({
request,
}) => {
test('other users personal envelopes should never appear regardless of context', async ({ request }) => {
const { team } = await seedTeam();
const member = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
const { user: outsider, team: outsiderTeam } = await seedUser();
@@ -1,23 +1,18 @@
import { PDF } from '@libpdf/core';
import type { APIRequestContext } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { DocumentStatus, FieldType, SigningStatus } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { PDF } from '@libpdf/core';
import type { APIRequestContext } from '@playwright/test';
import { expect, test } from '@playwright/test';
import { DocumentStatus, FieldType, SigningStatus } from '@prisma/client';
import { apiSeedDraftDocument, apiSeedPendingDocument } from '../../fixtures/api-seeds';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const API_BASE_URL = `${WEBAPP_BASE_URL}/api/v2-beta`;
const trpcMutation = async (
request: APIRequestContext,
procedure: string,
input: Record<string, unknown>,
) => {
const trpcMutation = async (request: APIRequestContext, procedure: string, input: Record<string, unknown>) => {
const res = await request.post(`${WEBAPP_BASE_URL}/api/trpc/${procedure}`, {
headers: {
'content-type': 'application/json',
@@ -63,9 +58,7 @@ const signAndCompleteRecipient = async ({
};
test.describe('API V2 partial signed PDF downloads', () => {
test('returns a PDF with inserted fields, supports ETag, and rejects after completion', async ({
request,
}) => {
test('returns a PDF with inserted fields, supports ETag, and rejects after completion', async ({ request }) => {
const { envelope, token, distributeResult } = await apiSeedPendingDocument(request, {
recipients: [
{ email: 'partial-signer-1@test.documenso.com', name: 'Partial Signer 1' },
@@ -118,9 +111,9 @@ test.describe('API V2 partial signed PDF downloads', () => {
});
expect(dbEnvelope.status).toBe(DocumentStatus.PENDING);
expect(
dbEnvelope.recipients.find((recipient) => recipient.id === recipientOne.id)?.signingStatus,
).toBe(SigningStatus.SIGNED);
expect(dbEnvelope.recipients.find((recipient) => recipient.id === recipientOne.id)?.signingStatus).toBe(
SigningStatus.SIGNED,
);
}).toPass();
const downloadUrl = `${API_BASE_URL}/envelope/item/${envelopeItem.id}/download?version=pending`;
@@ -1,10 +1,5 @@
import { PDF, StandardFonts } from '@libpdf/core';
import type { APIRequestContext } from '@playwright/test';
import { expect, test } from '@playwright/test';
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 { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
@@ -16,6 +11,10 @@ import type {
} 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 { TGetEnvelopeResponse } from '@documenso/trpc/server/envelope-router/get-envelope.types';
import { PDF, StandardFonts } from '@libpdf/core';
import type { APIRequestContext } from '@playwright/test';
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
@@ -66,11 +65,7 @@ test.describe('Placeholder-based field creation', () => {
return res.json();
};
const createEnvelopeItemsWithPdf = async (
request: APIRequestContext,
envelopeId: string,
pdfFilename: string,
) => {
const createEnvelopeItemsWithPdf = async (request: APIRequestContext, envelopeId: string, pdfFilename: string) => {
const pdfPath = path.join(FIXTURES_DIR, pdfFilename);
const pdfData = fs.readFileSync(pdfPath);
@@ -129,10 +124,7 @@ test.describe('Placeholder-based field creation', () => {
expect(res.ok()).toBeTruthy();
};
const getEnvelope = async (
request: APIRequestContext,
envelopeId: string,
): Promise<TGetEnvelopeResponse> => {
const getEnvelope = async (request: APIRequestContext, envelopeId: string): Promise<TGetEnvelopeResponse> => {
const res = await request.get(`${baseUrl}/envelope/${envelopeId}`, {
headers: { Authorization: `Bearer ${token}` },
});
@@ -281,9 +273,7 @@ test.describe('Placeholder-based field creation', () => {
expect(createFieldsRes.ok()).toBeFalsy();
});
test('should create fields using a mix of coordinate and placeholder positioning', async ({
request,
}) => {
test('should create fields using a mix of coordinate and placeholder positioning', async ({ request }) => {
const envelope = await createEnvelopeWithPdf(request, 'no-recipient-placeholders.pdf');
await addRecipient(request, envelope.id);
@@ -409,9 +399,7 @@ test.describe('Placeholder-based field creation', () => {
expect(uniqueYPositions.size).toBe(3);
});
test('should map placeholder recipients by signing order when adding items', async ({
request,
}) => {
test('should map placeholder recipients by signing order when adding items', async ({ request }) => {
const envelope = await createEnvelopeWithPdf(request, 'no-recipient-placeholders.pdf');
await addRecipients(request, envelope.id, [
@@ -1,5 +1,3 @@
import { expect, test } from '@playwright/test';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import type { TCheckboxFieldMeta, TRadioFieldMeta } from '@documenso/lib/types/field-meta';
@@ -12,16 +10,14 @@ import { prisma } from '@documenso/prisma';
import { FieldType, RecipientRole } from '@documenso/prisma/client';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import { apiSignin } from '../../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
test.describe('Template Field Prefill API v2', () => {
test('should create a document from template with prefilled fields', async ({
page,
request,
}) => {
test('should create a document from template with prefilled fields', async ({ page, request }) => {
// 1. Create a user
const { user, team } = await seedUser();
@@ -196,9 +192,7 @@ test.describe('Template Field Prefill API v2', () => {
});
// 7. Navigate to the template
await page.goto(
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
);
await page.goto(`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`);
// 8. Create a document from the template with prefilled fields using v2 API
const response = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
@@ -378,10 +372,7 @@ test.describe('Template Field Prefill API v2', () => {
await expect(page.getByText('Select B')).toBeVisible();
});
test('should create a document from template without prefilled fields', async ({
page,
request,
}) => {
test('should create a document from template without prefilled fields', async ({ page, request }) => {
// 1. Create a user
const { user, team } = await seedUser();
@@ -484,9 +475,7 @@ test.describe('Template Field Prefill API v2', () => {
});
// 7. Navigate to the template
await page.goto(
`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`,
);
await page.goto(`${WEBAPP_BASE_URL}/t/${team.url}/templates/${mapSecondaryIdToTemplateId(template.secondaryId)}`);
// 8. Create a document from the template without prefilled fields using v2 API
const response = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,10 @@
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
@@ -35,33 +34,23 @@ test.describe('Envelope Attachments API V2', () => {
});
test.describe('Envelope attachment find endpoint', () => {
test('should block unauthorized access to envelope attachment find endpoint', async ({
request,
}) => {
test('should block unauthorized access to envelope attachment find endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const res = await request.get(
`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment?envelopeId=${doc.id}`,
{
headers: { Authorization: `Bearer ${tokenB}` },
},
);
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment?envelopeId=${doc.id}`, {
headers: { Authorization: `Bearer ${tokenB}` },
});
expect(res.ok()).toBeFalsy();
expect(res.status()).toBe(404);
});
test('should allow authorized access to envelope attachment find endpoint', async ({
request,
}) => {
test('should allow authorized access to envelope attachment find endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const res = await request.get(
`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment?envelopeId=${doc.id}`,
{
headers: { Authorization: `Bearer ${tokenA}` },
},
);
const res = await request.get(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment?envelopeId=${doc.id}`, {
headers: { Authorization: `Bearer ${tokenA}` },
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
@@ -69,9 +58,7 @@ test.describe('Envelope Attachments API V2', () => {
});
test.describe('Envelope attachment create endpoint', () => {
test('should block unauthorized access to envelope attachment create endpoint', async ({
request,
}) => {
test('should block unauthorized access to envelope attachment create endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment/create`, {
@@ -89,9 +76,7 @@ test.describe('Envelope Attachments API V2', () => {
expect(res.status()).toBe(404);
});
test('should allow authorized access to envelope attachment create endpoint', async ({
request,
}) => {
test('should allow authorized access to envelope attachment create endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/attachment/create`, {
@@ -111,9 +96,7 @@ test.describe('Envelope Attachments API V2', () => {
});
test.describe('Envelope attachment update endpoint', () => {
test('should block unauthorized access to envelope attachment update endpoint', async ({
request,
}) => {
test('should block unauthorized access to envelope attachment update endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const attachment = await prisma.envelopeAttachment.create({
@@ -140,9 +123,7 @@ test.describe('Envelope Attachments API V2', () => {
expect(res.status()).toBe(404);
});
test('should allow authorized access to envelope attachment update endpoint', async ({
request,
}) => {
test('should allow authorized access to envelope attachment update endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const attachment = await prisma.envelopeAttachment.create({
@@ -171,9 +152,7 @@ test.describe('Envelope Attachments API V2', () => {
});
test.describe('Envelope attachment delete endpoint', () => {
test('should block unauthorized access to envelope attachment delete endpoint', async ({
request,
}) => {
test('should block unauthorized access to envelope attachment delete endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const attachment = await prisma.envelopeAttachment.create({
@@ -194,9 +173,7 @@ test.describe('Envelope Attachments API V2', () => {
expect(res.status()).toBe(404);
});
test('should allow authorized access to envelope attachment delete endpoint', async ({
request,
}) => {
test('should allow authorized access to envelope attachment delete endpoint', async ({ request }) => {
const doc = await seedBlankDocument(userA, teamA.id);
const attachment = await prisma.envelopeAttachment.create({
@@ -1,13 +1,12 @@
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { EnvelopeType, FolderType } from '@prisma/client';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
import { seedUser } from '@documenso/prisma/seed/users';
import { expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
import { EnvelopeType, FolderType } from '@prisma/client';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
@@ -16,375 +15,366 @@ test.describe.configure({
});
// Todo: Remove skip once the API endpoints are released.
test.describe.skip('Envelope Bulk API V2', () => {
let userA: User, teamA: Team, userB: User, teamB: Team, tokenA: string, tokenB: string;
test.describe
.skip('Envelope Bulk API V2', () => {
let userA: User, teamA: Team, userB: User, teamB: Team, tokenA: string, tokenB: string;
test.beforeEach(async () => {
({ user: userA, team: teamA } = await seedUser());
({ token: tokenA } = await createApiToken({
userId: userA.id,
teamId: teamA.id,
tokenName: 'userA',
expiresIn: null,
}));
test.beforeEach(async () => {
({ user: userA, team: teamA } = await seedUser());
({ token: tokenA } = await createApiToken({
userId: userA.id,
teamId: teamA.id,
tokenName: 'userA',
expiresIn: null,
}));
({ user: userB, team: teamB } = await seedUser());
({ token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
}));
});
test.describe('Envelope bulk move endpoint', () => {
test('should block unauthorized access to envelope bulk move endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// UserB tries to move userA's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: null,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.movedCount).toBe(0);
// Verify in database that the document was not modified
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBeNull();
({ user: userB, team: teamB } = await seedUser());
({ token: tokenB } = await createApiToken({
userId: userB.id,
teamId: teamB.id,
tokenName: 'userB',
expiresIn: null,
}));
});
test('should block moving envelopes to unauthorized folder', async ({ request }) => {
// Create a document owned by userB
const doc = await seedBlankDocument(userB, teamB.id);
test.describe('Envelope bulk move endpoint', () => {
test('should block unauthorized access to envelope bulk move endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
// UserB tries to move userA's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: null,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.movedCount).toBe(0);
// Verify in database that the document was not modified
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBeNull();
});
// UserB tries to move their document to userA's folder
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
test('should block moving envelopes to unauthorized folder', async ({ request }) => {
// Create a document owned by userB
const doc = await seedBlankDocument(userB, teamB.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
});
// UserB tries to move their document to userA's folder
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeFalsy();
expect(res.status()).toBe(404);
// Verify in database that the document was not modified
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBeNull();
});
expect(res.ok()).toBeFalsy();
expect(res.status()).toBe(404);
test('should allow authorized access to envelope bulk move endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// Verify in database that the document was not modified
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
});
// UserA moves their own document to their own folder
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.movedCount).toBe(1);
// Verify in database that the document was moved to the folder
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBe(folderA.id);
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBeNull();
test('should only move authorized envelopes when given mixed array of envelope IDs', async ({ request }) => {
// Create documents owned by userA
const docA1 = await seedBlankDocument(userA, teamA.id);
const docA2 = await seedBlankDocument(userA, teamA.id);
// Create a document owned by userB
const docB = await seedBlankDocument(userB, teamB.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
});
// UserA tries to move a mix of their own documents and userB's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docA1.id, docB.id, docA2.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
// Only userA's documents should be moved
expect(body.movedCount).toBe(2);
// Verify userA's documents were moved
const docA1InDb = await prisma.envelope.findFirst({
where: { id: docA1.id },
});
expect(docA1InDb).not.toBeNull();
expect(docA1InDb?.folderId).toBe(folderA.id);
const docA2InDb = await prisma.envelope.findFirst({
where: { id: docA2.id },
});
expect(docA2InDb).not.toBeNull();
expect(docA2InDb?.folderId).toBe(folderA.id);
// Verify userB's document was NOT moved
const docBInDb = await prisma.envelope.findFirst({
where: { id: docB.id },
});
expect(docBInDb).not.toBeNull();
expect(docBInDb?.folderId).toBeNull();
});
test('should move zero envelopes when all envelope IDs in array are unauthorized', async ({ request }) => {
// Create documents owned by userB
const docB1 = await seedBlankDocument(userB, teamB.id);
const docB2 = await seedBlankDocument(userB, teamB.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
});
// UserA tries to move userB's documents
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docB1.id, docB2.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.movedCount).toBe(0);
// Verify userB's documents were NOT moved
const docB1InDb = await prisma.envelope.findFirst({
where: { id: docB1.id },
});
expect(docB1InDb).not.toBeNull();
expect(docB1InDb?.folderId).toBeNull();
const docB2InDb = await prisma.envelope.findFirst({
where: { id: docB2.id },
});
expect(docB2InDb).not.toBeNull();
expect(docB2InDb?.folderId).toBeNull();
});
});
test('should allow authorized access to envelope bulk move endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
test.describe('Envelope bulk delete endpoint', () => {
test('should block unauthorized access to envelope bulk delete endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
// UserB tries to delete userA's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.deletedCount).toBe(0);
// Unauthorized envelope ID should be in failedIds
expect(body.failedIds).toEqual([doc.id]);
// Verify in database that the document still exists
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.id).toBe(doc.id);
expect(docInDb?.deletedAt).toBeNull();
});
// UserA moves their own document to their own folder
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [doc.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
test('should allow authorized access to envelope bulk delete endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// UserA deletes their own document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [doc.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.deletedCount).toBe(1);
expect(body.failedIds).toEqual([]);
// Verify in database that the document no longer exists
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).toBeNull();
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
test('should only delete authorized envelopes when given mixed array of envelope IDs', async ({ request }) => {
// Create documents owned by userA
const docA1 = await seedBlankDocument(userA, teamA.id);
const docA2 = await seedBlankDocument(userA, teamA.id);
const body = await res.json();
expect(body.movedCount).toBe(1);
// Create a document owned by userB
const docB = await seedBlankDocument(userB, teamB.id);
// Verify in database that the document was moved to the folder
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
// UserA tries to delete a mix of their own documents and userB's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docA1.id, docB.id, docA2.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
// Only userA's documents should be deleted
expect(body.deletedCount).toBe(2);
// Unauthorized envelope ID (docB) should be in failedIds
expect(body.failedIds).toEqual([docB.id]);
// Verify userA's documents were deleted
const docA1InDb = await prisma.envelope.findFirst({
where: { id: docA1.id },
});
expect(docA1InDb).toBeNull();
const docA2InDb = await prisma.envelope.findFirst({
where: { id: docA2.id },
});
expect(docA2InDb).toBeNull();
// Verify userB's document was NOT deleted
const docBInDb = await prisma.envelope.findFirst({
where: { id: docB.id },
});
expect(docBInDb).not.toBeNull();
expect(docBInDb?.id).toBe(docB.id);
expect(docBInDb?.deletedAt).toBeNull();
});
expect(docInDb).not.toBeNull();
expect(docInDb?.folderId).toBe(folderA.id);
});
test('should delete zero envelopes when all envelope IDs in array are unauthorized', async ({ request }) => {
// Create documents owned by userB
const docB1 = await seedBlankDocument(userB, teamB.id);
const docB2 = await seedBlankDocument(userB, teamB.id);
test('should only move authorized envelopes when given mixed array of envelope IDs', async ({
request,
}) => {
// Create documents owned by userA
const docA1 = await seedBlankDocument(userA, teamA.id);
const docA2 = await seedBlankDocument(userA, teamA.id);
// UserA tries to delete userB's documents
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docB1.id, docB2.id],
},
});
// Create a document owned by userB
const docB = await seedBlankDocument(userB, teamB.id);
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
const body = await res.json();
expect(body.deletedCount).toBe(0);
// All unauthorized envelope IDs should be in failedIds
expect(body.failedIds).toEqual(expect.arrayContaining([docB1.id, docB2.id]));
expect(body.failedIds).toHaveLength(2);
// Verify userB's documents were NOT deleted
const docB1InDb = await prisma.envelope.findFirst({
where: { id: docB1.id },
});
expect(docB1InDb).not.toBeNull();
expect(docB1InDb?.id).toBe(docB1.id);
expect(docB1InDb?.deletedAt).toBeNull();
const docB2InDb = await prisma.envelope.findFirst({
where: { id: docB2.id },
});
expect(docB2InDb).not.toBeNull();
expect(docB2InDb?.id).toBe(docB2.id);
expect(docB2InDb?.deletedAt).toBeNull();
});
// UserA tries to move a mix of their own documents and userB's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docA1.id, docB.id, docA2.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
// Only userA's documents should be moved
expect(body.movedCount).toBe(2);
// Verify userA's documents were moved
const docA1InDb = await prisma.envelope.findFirst({
where: { id: docA1.id },
});
expect(docA1InDb).not.toBeNull();
expect(docA1InDb?.folderId).toBe(folderA.id);
const docA2InDb = await prisma.envelope.findFirst({
where: { id: docA2.id },
});
expect(docA2InDb).not.toBeNull();
expect(docA2InDb?.folderId).toBe(folderA.id);
// Verify userB's document was NOT moved
const docBInDb = await prisma.envelope.findFirst({
where: { id: docB.id },
});
expect(docBInDb).not.toBeNull();
expect(docBInDb?.folderId).toBeNull();
});
test('should move zero envelopes when all envelope IDs in array are unauthorized', async ({
request,
}) => {
// Create documents owned by userB
const docB1 = await seedBlankDocument(userB, teamB.id);
const docB2 = await seedBlankDocument(userB, teamB.id);
// Create a folder owned by userA
const folderA = await seedBlankFolder(userA, teamA.id, {
createFolderOptions: {
name: 'UserA Folder',
type: FolderType.DOCUMENT,
},
});
// UserA tries to move userB's documents
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/move`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docB1.id, docB2.id],
envelopeType: EnvelopeType.DOCUMENT,
folderId: folderA.id,
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.movedCount).toBe(0);
// Verify userB's documents were NOT moved
const docB1InDb = await prisma.envelope.findFirst({
where: { id: docB1.id },
});
expect(docB1InDb).not.toBeNull();
expect(docB1InDb?.folderId).toBeNull();
const docB2InDb = await prisma.envelope.findFirst({
where: { id: docB2.id },
});
expect(docB2InDb).not.toBeNull();
expect(docB2InDb?.folderId).toBeNull();
});
});
test.describe('Envelope bulk delete endpoint', () => {
test('should block unauthorized access to envelope bulk delete endpoint', async ({
request,
}) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// UserB tries to delete userA's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenB}` },
data: {
envelopeIds: [doc.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.deletedCount).toBe(0);
// Unauthorized envelope ID should be in failedIds
expect(body.failedIds).toEqual([doc.id]);
// Verify in database that the document still exists
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).not.toBeNull();
expect(docInDb?.id).toBe(doc.id);
expect(docInDb?.deletedAt).toBeNull();
});
test('should allow authorized access to envelope bulk delete endpoint', async ({ request }) => {
// Create a document owned by userA
const doc = await seedBlankDocument(userA, teamA.id);
// UserA deletes their own document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [doc.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.deletedCount).toBe(1);
expect(body.failedIds).toEqual([]);
// Verify in database that the document no longer exists
const docInDb = await prisma.envelope.findFirst({
where: { id: doc.id },
});
expect(docInDb).toBeNull();
});
test('should only delete authorized envelopes when given mixed array of envelope IDs', async ({
request,
}) => {
// Create documents owned by userA
const docA1 = await seedBlankDocument(userA, teamA.id);
const docA2 = await seedBlankDocument(userA, teamA.id);
// Create a document owned by userB
const docB = await seedBlankDocument(userB, teamB.id);
// UserA tries to delete a mix of their own documents and userB's document
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docA1.id, docB.id, docA2.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
// Only userA's documents should be deleted
expect(body.deletedCount).toBe(2);
// Unauthorized envelope ID (docB) should be in failedIds
expect(body.failedIds).toEqual([docB.id]);
// Verify userA's documents were deleted
const docA1InDb = await prisma.envelope.findFirst({
where: { id: docA1.id },
});
expect(docA1InDb).toBeNull();
const docA2InDb = await prisma.envelope.findFirst({
where: { id: docA2.id },
});
expect(docA2InDb).toBeNull();
// Verify userB's document was NOT deleted
const docBInDb = await prisma.envelope.findFirst({
where: { id: docB.id },
});
expect(docBInDb).not.toBeNull();
expect(docBInDb?.id).toBe(docB.id);
expect(docBInDb?.deletedAt).toBeNull();
});
test('should delete zero envelopes when all envelope IDs in array are unauthorized', async ({
request,
}) => {
// Create documents owned by userB
const docB1 = await seedBlankDocument(userB, teamB.id);
const docB2 = await seedBlankDocument(userB, teamB.id);
// UserA tries to delete userB's documents
const res = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/envelope/bulk/delete`, {
headers: { Authorization: `Bearer ${tokenA}` },
data: {
envelopeIds: [docB1.id, docB2.id],
},
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const body = await res.json();
expect(body.deletedCount).toBe(0);
// All unauthorized envelope IDs should be in failedIds
expect(body.failedIds).toEqual(expect.arrayContaining([docB1.id, docB2.id]));
expect(body.failedIds).toHaveLength(2);
// Verify userB's documents were NOT deleted
const docB1InDb = await prisma.envelope.findFirst({
where: { id: docB1.id },
});
expect(docB1InDb).not.toBeNull();
expect(docB1InDb?.id).toBe(docB1.id);
expect(docB1InDb?.deletedAt).toBeNull();
const docB2InDb = await prisma.envelope.findFirst({
where: { id: docB2.id },
});
expect(docB2InDb).not.toBeNull();
expect(docB2InDb?.id).toBe(docB2.id);
expect(docB2InDb?.deletedAt).toBeNull();
});
});
});
@@ -1,8 +1,5 @@
import { type APIRequestContext, expect, test } from '@playwright/test';
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 { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { prisma } from '@documenso/prisma';
@@ -16,6 +13,8 @@ import type { TDistributeEnvelopeRequest } from '@documenso/trpc/server/envelope
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 { TUpdateEnvelopeItemsRequest } from '@documenso/trpc/server/envelope-router/update-envelope-items.types';
import { type APIRequestContext, expect, test } from '@playwright/test';
import type { Team, User } from '@prisma/client';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
@@ -64,11 +63,7 @@ const getEnvelope = async (request: APIRequestContext, authToken: string, envelo
* Transition an envelope from DRAFT to PENDING by adding a recipient with a
* signature field and distributing.
*/
const distributeEnvelope = async (
request: APIRequestContext,
authToken: string,
envelopeId: string,
) => {
const distributeEnvelope = async (request: APIRequestContext, authToken: string, envelopeId: string) => {
const recipientEmail = `signer-${Date.now()}@test.documenso.com`;
// Create a SIGNER recipient.
@@ -228,9 +223,7 @@ test.describe('Update envelope items', () => {
expect(dbItem.title).toBe('Updated Pending Title');
});
test('should allow title-only update when order matches existing on a PENDING envelope', async ({
request,
}) => {
test('should allow title-only update when order matches existing on a PENDING envelope', async ({ request }) => {
const envelope = await createEnvelope(request, token);
await distributeEnvelope(request, token, envelope.id);
@@ -318,9 +311,7 @@ test.describe('Update envelope items', () => {
expect(res.status()).toBe(400);
});
test('should reject combined title and order change on a PENDING envelope', async ({
request,
}) => {
test('should reject combined title and order change on a PENDING envelope', async ({ request }) => {
const envelope = await createEnvelope(request, token);
await distributeEnvelope(request, token, envelope.id);
@@ -499,9 +490,7 @@ test.describe('Update envelope items', () => {
expect(res.ok()).toBeFalsy();
});
test('should reject update for an envelope item that does not belong to the envelope', async ({
request,
}) => {
test('should reject update for an envelope item that does not belong to the envelope', async ({ request }) => {
const envelopeA = await createEnvelope(request, token);
const envelopeB = await createEnvelope(request, token);