Files
documenso/packages/app-tests/e2e/teams/default-recipients.spec.ts
T
2026-05-08 16:04:22 +10:00

415 lines
13 KiB
TypeScript

import * as fs from 'node:fs';
import * as 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';
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
import { seedTeam } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
import type {
TCreateEnvelopePayload,
TCreateEnvelopeResponse,
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
import { expect, test } from '@playwright/test';
import { apiSignin } from '../fixtures/authentication';
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
test.describe.configure({ mode: 'parallel' });
/**
* Helper function to set default recipients for a team
*/
const setTeamDefaultRecipients = async (
teamId: number,
defaultRecipients: Array<{ email: string; name: string; role: RecipientRole }>,
) => {
const teamSettings = await prisma.teamGlobalSettings.findFirstOrThrow({
where: {
team: {
id: teamId,
},
},
});
await prisma.teamGlobalSettings.update({
where: {
id: teamSettings.id,
},
data: {
defaultRecipients,
},
});
};
test.describe('Default Recipients', () => {
test('[DEFAULT_RECIPIENTS]: default recipients are added to documents created via UI', async ({ page }) => {
const { team, owner } = await seedTeam({
createTeamMembers: 2,
});
// Get a team member to set as default recipient
const teamMembers = await prisma.organisationMember.findMany({
where: {
organisationId: team.organisationId,
userId: {
not: owner.id,
},
},
include: {
user: true,
},
});
const defaultRecipientUser = teamMembers[0].user;
// Set up default recipients for the team
await setTeamDefaultRecipients(team.id, [
{
email: defaultRecipientUser.email,
name: defaultRecipientUser.name || defaultRecipientUser.email,
role: RecipientRole.CC,
},
]);
await apiSignin({
page,
email: owner.email,
redirectPath: `/t/${team.url}/documents`,
});
// Upload document via UI - this triggers document creation with default recipients
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page
.locator('input[type=file]')
.nth(1)
.evaluate((e) => {
if (e instanceof HTMLInputElement) {
e.click();
}
}),
]);
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
// Wait to be redirected to the edit page (v2 envelope editor)
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
// Extract document ID from URL
const urlParts = page.url().split('/');
const documentId = urlParts.find((part) => part.startsWith('envelope_'));
// Wait for the Recipients card to be visible (v2 envelope editor)
await expect(page.getByRole('heading', { name: 'Recipients' })).toBeVisible();
await expect(page.getByTestId('signer-email-input').first()).not.toBeEmpty();
await page.getByRole('button', { name: 'Add Signer' }).click();
// Add a regular signer using the v2 editor
await page.getByTestId('signer-email-input').last().fill('regular-signer@documenso.com');
await page
.getByPlaceholder(/Recipient/)
.first()
.fill('Regular Signer');
// Wait for autosave to complete
await page.waitForTimeout(3000);
// Verify that default recipient is present in the database
await expect(async () => {
const envelope = await prisma.envelope.findFirstOrThrow({
where: {
id: documentId,
},
include: {
recipients: true,
},
});
// Should have 2 recipients: the regular signer + the default recipient
expect(envelope.recipients.length).toBe(2);
const defaultRecipient = envelope.recipients.find(
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
);
expect(defaultRecipient).toBeDefined();
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
const regularSigner = envelope.recipients.find((r) => r.email === 'regular-signer@documenso.com');
expect(regularSigner).toBeDefined();
}).toPass();
});
// TODO: Are we intending to allow default recipients to be removed from a document?
test.skip('[DEFAULT_RECIPIENTS]: default recipients cannot be removed from a document', async ({ page }) => {
const { team, owner } = await seedTeam({
createTeamMembers: 2,
});
// Get a team member to set as default recipient
const teamMembers = await prisma.organisationMember.findMany({
where: {
organisationId: team.organisationId,
userId: {
not: owner.id,
},
},
include: {
user: true,
},
});
const defaultRecipientUser = teamMembers[0].user;
// Set up default recipients for the team
await setTeamDefaultRecipients(team.id, [
{
email: defaultRecipientUser.email,
name: defaultRecipientUser.name || defaultRecipientUser.email,
role: RecipientRole.CC,
},
]);
await apiSignin({
page,
email: owner.email,
redirectPath: `/t/${team.url}/documents`,
});
// Upload document via UI - this triggers document creation with default recipients
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page
.locator('input[type=file]')
.nth(1)
.evaluate((e) => {
if (e instanceof HTMLInputElement) {
e.click();
}
}),
]);
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
// Wait to be redirected to the edit page (v2 envelope editor)
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
// Extract document ID from URL
const urlParts = page.url().split('/');
const documentId = urlParts.find((part) => part.startsWith('envelope_'));
// Replace the default recipient with a regular signer
await page.getByTestId('signer-email-input').first().fill('regular-signer@documenso.com');
await page
.getByPlaceholder(/Recipient/)
.first()
.fill('Regular Signer');
// Wait for autosave to complete
await page.waitForTimeout(3000);
// Wait for recipients to be saved
await expect(async () => {
const envelope = await prisma.envelope.findFirstOrThrow({
where: {
id: documentId,
},
include: {
recipients: true,
},
});
expect(envelope.recipients.length).toBe(2);
}).toPass();
// Verify that the default recipient's remove button is disabled
// In the v2 editor, default recipients should have a disabled remove button
// Find the fieldset containing the default recipient's email and check if its remove button is disabled
const defaultRecipientRow = page.locator('fieldset').filter({
hasText: defaultRecipientUser.email,
});
// The default recipient row should exist and have a disabled remove button
await expect(defaultRecipientRow).toHaveCount(1);
const removeButton = defaultRecipientRow.getByTestId('remove-signer-button');
await expect(removeButton).toBeDisabled();
});
test('[DEFAULT_RECIPIENTS]: documents created via API have default recipients', async ({ request }) => {
const { team, owner } = await seedTeam({
createTeamMembers: 2,
});
// Get a team member to set as default recipient
const teamMembers = await prisma.organisationMember.findMany({
where: {
organisationId: team.organisationId,
userId: {
not: owner.id,
},
},
include: {
user: true,
},
});
const defaultRecipientUser = teamMembers[0].user;
// Set up default recipients for the team
await setTeamDefaultRecipients(team.id, [
{
email: defaultRecipientUser.email,
name: defaultRecipientUser.name || defaultRecipientUser.email,
role: RecipientRole.CC,
},
]);
// Create API token
const { token } = await createApiToken({
userId: owner.id,
teamId: team.id,
tokenName: 'test-token',
expiresIn: null,
});
// Create envelope via API
const payload: TCreateEnvelopePayload = {
type: EnvelopeType.DOCUMENT,
title: 'Test Document with Default Recipients',
recipients: [
{
email: 'api-recipient@documenso.com',
name: 'API Recipient',
role: RecipientRole.SIGNER,
},
],
};
const formData = new FormData();
formData.append('payload', JSON.stringify(payload));
const pdfData = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));
formData.append('files', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
const res = await request.post(`${baseUrl}/envelope/create`, {
headers: { Authorization: `Bearer ${token}` },
multipart: formData,
});
expect(res.ok()).toBeTruthy();
expect(res.status()).toBe(200);
const response = (await res.json()) as TCreateEnvelopeResponse;
// Verify the envelope has both the API recipient and the default recipient
const envelope = await prisma.envelope.findUniqueOrThrow({
where: {
id: response.id,
},
include: {
recipients: true,
},
});
expect(envelope.recipients.length).toBe(2);
const apiRecipient = envelope.recipients.find((r) => r.email === 'api-recipient@documenso.com');
expect(apiRecipient).toBeDefined();
expect(apiRecipient?.role).toBe(RecipientRole.SIGNER);
const defaultRecipient = envelope.recipients.find(
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
);
expect(defaultRecipient).toBeDefined();
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
});
test('[DEFAULT_RECIPIENTS]: documents created from template have default recipients', async ({ page }) => {
const { team, owner } = await seedTeam({
createTeamMembers: 2,
});
// Get a team member to set as default recipient
const teamMembers = await prisma.organisationMember.findMany({
where: {
organisationId: team.organisationId,
userId: {
not: owner.id,
},
},
include: {
user: true,
},
});
const defaultRecipientUser = teamMembers[0].user;
// Set up default recipients for the team
await setTeamDefaultRecipients(team.id, [
{
email: defaultRecipientUser.email,
name: defaultRecipientUser.name || defaultRecipientUser.email,
role: RecipientRole.CC,
},
]);
// Create a template
const template = await seedBlankTemplate(owner, team.id);
await apiSignin({
page,
email: owner.email,
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
});
// Set template title
await page.getByLabel('Title').fill('Template with Default Recipients');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
// Add a template recipient
await page.getByPlaceholder('Email').fill('template-recipient@documenso.com');
await page.getByPlaceholder('Name').fill('Template Recipient');
await page.getByRole('button', { name: 'Continue' }).click();
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
await page.getByRole('button', { name: 'Save template' }).click();
// Use template to create document
await page.waitForURL(`/t/${team.url}/templates`);
await page.getByRole('button', { name: 'Use Template' }).click();
await page.getByRole('button', { name: 'Create as draft' }).click();
// Wait for document to be created
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
const documentId = page.url().split('/').pop();
// Verify the document has both the template recipient and the default recipient
const document = await prisma.envelope.findFirstOrThrow({
where: {
id: documentId,
},
include: {
recipients: true,
},
});
expect(document.recipients.length).toBe(2);
const templateRecipient = document.recipients.find((r) => r.email === 'template-recipient@documenso.com');
expect(templateRecipient).toBeDefined();
const defaultRecipient = document.recipients.find(
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
);
expect(defaultRecipient).toBeDefined();
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
});
});