mirror of
https://github.com/documenso/documenso.git
synced 2025-11-12 15:53:02 +10:00
This change allows API users to pre-fill fields with values by
passing the data in the request body. Example body for V2 API endpoint
`/api/v2-beta/template/use`:
```json
{
"templateId": 1,
"recipients": [
{
"id": 1,
"email": "signer1@mail.com",
"name": "Signer 1"
},
{
"id": 2,
"email": "signer2@mail.com",
"name": "Signer 2"
}
],
"prefillValues": [
{
"id": 14,
"fieldMeta": {
"type": "text",
"label": "my label",
"placeholder": "text placeholder test",
"text": "auto-sign value",
"characterLimit": 25,
"textAlign": "right",
"fontSize": 94,
"required": true
}
},
{
"id": 15,
"fieldMeta": {
"type": "radio",
"label": "radio label",
"placeholder": "new radio placeholder",
"required": false,
"readOnly": true,
"values": [
{
"id": 2,
"checked": true,
"value": "radio val 1"
},
{
"id": 3,
"checked": false,
"value": "radio val 2"
}
]
}
},
{
"id": 16,
"fieldMeta": {
"type": "dropdown",
"label": "dropdown label",
"placeholder": "DD placeholder",
"required": false,
"readOnly": false,
"values": [
{
"value": "option 1"
},
{
"value": "option 2"
},
{
"value": "option 3"
}
],
"defaultValue": "option 2"
}
}
],
"distributeDocument": false,
"customDocumentDataId": ""
}
```
601 lines
17 KiB
TypeScript
601 lines
17 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
import { WEBAPP_BASE_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';
|
|
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 { apiSignin } from '../../fixtures/authentication';
|
|
|
|
test.describe('Template Field Prefill API v2', () => {
|
|
test('should create a document from template with prefilled fields', async ({
|
|
page,
|
|
request,
|
|
}) => {
|
|
// 1. Create a user
|
|
const user = await seedUser();
|
|
|
|
// 2. Create an API token for the user
|
|
const { token } = await createApiToken({
|
|
userId: user.id,
|
|
tokenName: 'test-token',
|
|
expiresIn: null,
|
|
});
|
|
|
|
// 3. Create a template with seedBlankTemplate
|
|
const template = await seedBlankTemplate(user, {
|
|
createTemplateOptions: {
|
|
title: 'Template with Advanced Fields V2',
|
|
},
|
|
});
|
|
|
|
// 4. Create a recipient for the template
|
|
const recipient = await prisma.recipient.create({
|
|
data: {
|
|
templateId: template.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
role: RecipientRole.SIGNER,
|
|
token: 'test-token',
|
|
readStatus: 'NOT_OPENED',
|
|
sendStatus: 'NOT_SENT',
|
|
signingStatus: 'NOT_SIGNED',
|
|
},
|
|
});
|
|
|
|
// 5. Add fields to the template
|
|
// Add TEXT field
|
|
const textField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.TEXT,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 5,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'text',
|
|
label: 'Text Field',
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add NUMBER field
|
|
const numberField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.NUMBER,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 15,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'number',
|
|
label: 'Number Field',
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add RADIO field
|
|
const radioField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.RADIO,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 25,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'radio',
|
|
label: 'Radio Field',
|
|
values: [
|
|
{ id: 1, value: 'Option A', checked: false },
|
|
{ id: 2, value: 'Option B', checked: false },
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add CHECKBOX field
|
|
const checkboxField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.CHECKBOX,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 35,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'checkbox',
|
|
label: 'Checkbox Field',
|
|
values: [
|
|
{ id: 1, value: 'Check A', checked: false },
|
|
{ id: 2, value: 'Check B', checked: false },
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add DROPDOWN field
|
|
const dropdownField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.DROPDOWN,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 45,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'dropdown',
|
|
label: 'Dropdown Field',
|
|
values: [{ value: 'Select A' }, { value: 'Select B' }],
|
|
},
|
|
},
|
|
});
|
|
|
|
// 6. Sign in as the user
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
});
|
|
|
|
// 7. Navigate to the template
|
|
await page.goto(`${WEBAPP_BASE_URL}/templates/${template.id}`);
|
|
|
|
// 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`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
templateId: template.id,
|
|
recipients: [
|
|
{
|
|
id: recipient.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
},
|
|
],
|
|
prefillFields: [
|
|
{
|
|
id: textField.id,
|
|
type: 'text',
|
|
label: 'Prefilled Text',
|
|
value: 'This is prefilled text',
|
|
},
|
|
{
|
|
id: numberField.id,
|
|
type: 'number',
|
|
label: 'Prefilled Number',
|
|
value: '42',
|
|
},
|
|
{
|
|
id: radioField.id,
|
|
type: 'radio',
|
|
label: 'Prefilled Radio',
|
|
value: 'Option A',
|
|
},
|
|
{
|
|
id: checkboxField.id,
|
|
type: 'checkbox',
|
|
label: 'Prefilled Checkbox',
|
|
value: ['Check A', 'Check B'],
|
|
},
|
|
{
|
|
id: dropdownField.id,
|
|
type: 'dropdown',
|
|
label: 'Prefilled Dropdown',
|
|
value: 'Select B',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
expect(response.ok()).toBeTruthy();
|
|
expect(response.status()).toBe(200);
|
|
|
|
expect(responseData.id).toBeDefined();
|
|
|
|
// 9. Verify the document was created with prefilled fields
|
|
const document = await prisma.document.findUnique({
|
|
where: {
|
|
id: responseData.id,
|
|
},
|
|
include: {
|
|
fields: true,
|
|
},
|
|
});
|
|
|
|
expect(document).not.toBeNull();
|
|
|
|
// 10. Verify each field has the correct prefilled values
|
|
const documentTextField = document?.fields.find(
|
|
(field) => field.type === FieldType.TEXT && field.fieldMeta?.type === 'text',
|
|
);
|
|
expect(documentTextField?.fieldMeta).toMatchObject({
|
|
type: 'text',
|
|
label: 'Prefilled Text',
|
|
text: 'This is prefilled text',
|
|
});
|
|
|
|
const documentNumberField = document?.fields.find(
|
|
(field) => field.type === FieldType.NUMBER && field.fieldMeta?.type === 'number',
|
|
);
|
|
expect(documentNumberField?.fieldMeta).toMatchObject({
|
|
type: 'number',
|
|
label: 'Prefilled Number',
|
|
value: '42',
|
|
});
|
|
|
|
const documentRadioField = document?.fields.find(
|
|
(field) => field.type === FieldType.RADIO && field.fieldMeta?.type === 'radio',
|
|
);
|
|
expect(documentRadioField?.fieldMeta).toMatchObject({
|
|
type: 'radio',
|
|
label: 'Prefilled Radio',
|
|
});
|
|
// Check that the correct radio option is selected
|
|
const radioValues = (documentRadioField?.fieldMeta as TRadioFieldMeta)?.values || [];
|
|
const selectedRadioOption = radioValues.find((option) => option.checked);
|
|
expect(selectedRadioOption?.value).toBe('Option A');
|
|
|
|
const documentCheckboxField = document?.fields.find(
|
|
(field) => field.type === FieldType.CHECKBOX && field.fieldMeta?.type === 'checkbox',
|
|
);
|
|
expect(documentCheckboxField?.fieldMeta).toMatchObject({
|
|
type: 'checkbox',
|
|
label: 'Prefilled Checkbox',
|
|
});
|
|
// Check that the correct checkbox options are selected
|
|
const checkboxValues = (documentCheckboxField?.fieldMeta as TCheckboxFieldMeta)?.values || [];
|
|
const checkedOptions = checkboxValues.filter((option) => option.checked);
|
|
expect(checkedOptions.length).toBe(2);
|
|
expect(checkedOptions.map((option) => option.value)).toContain('Check A');
|
|
expect(checkedOptions.map((option) => option.value)).toContain('Check B');
|
|
|
|
const documentDropdownField = document?.fields.find(
|
|
(field) => field.type === FieldType.DROPDOWN && field.fieldMeta?.type === 'dropdown',
|
|
);
|
|
expect(documentDropdownField?.fieldMeta).toMatchObject({
|
|
type: 'dropdown',
|
|
label: 'Prefilled Dropdown',
|
|
defaultValue: 'Select B',
|
|
});
|
|
|
|
const sendResponse = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/document/distribute`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
documentId: document?.id,
|
|
meta: {
|
|
subject: 'Test Subject',
|
|
message: 'Test Message',
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(sendResponse.ok()).toBeTruthy();
|
|
await expect(sendResponse.status()).toBe(200);
|
|
|
|
// 11. Sign in as the recipient and verify the prefilled fields are visible
|
|
const documentRecipient = await prisma.recipient.findFirst({
|
|
where: {
|
|
documentId: document?.id,
|
|
email: 'recipient@example.com',
|
|
},
|
|
});
|
|
|
|
expect(documentRecipient).not.toBeNull();
|
|
|
|
// Visit the signing page
|
|
await page.goto(`${WEBAPP_BASE_URL}/sign/${documentRecipient?.token}`);
|
|
|
|
// Verify the prefilled fields are visible with correct values
|
|
// Text field
|
|
await expect(page.getByText('This is prefilled')).toBeVisible();
|
|
|
|
// Number field
|
|
await expect(page.getByText('42')).toBeVisible();
|
|
|
|
// Radio field
|
|
await expect(page.getByText('Option A')).toBeVisible();
|
|
await expect(page.getByRole('radio', { name: 'Option A' })).toBeChecked();
|
|
|
|
// Checkbox field
|
|
await expect(page.getByText('Check A')).toBeVisible();
|
|
await expect(page.getByText('Check B')).toBeVisible();
|
|
await expect(page.getByRole('checkbox', { name: 'Check A' })).toBeChecked();
|
|
await expect(page.getByRole('checkbox', { name: 'Check B' })).toBeChecked();
|
|
|
|
// Dropdown field
|
|
await expect(page.getByText('Select B')).toBeVisible();
|
|
});
|
|
|
|
test('should create a document from template without prefilled fields', async ({
|
|
page,
|
|
request,
|
|
}) => {
|
|
// 1. Create a user
|
|
const user = await seedUser();
|
|
|
|
// 2. Create an API token for the user
|
|
const { token } = await createApiToken({
|
|
userId: user.id,
|
|
tokenName: 'test-token',
|
|
expiresIn: null,
|
|
});
|
|
|
|
// 3. Create a template with seedBlankTemplate
|
|
const template = await seedBlankTemplate(user, {
|
|
createTemplateOptions: {
|
|
title: 'Template with Default Fields V2',
|
|
},
|
|
});
|
|
|
|
// 4. Create a recipient for the template
|
|
const recipient = await prisma.recipient.create({
|
|
data: {
|
|
templateId: template.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
role: RecipientRole.SIGNER,
|
|
token: 'test-token',
|
|
readStatus: 'NOT_OPENED',
|
|
sendStatus: 'NOT_SENT',
|
|
signingStatus: 'NOT_SIGNED',
|
|
},
|
|
});
|
|
|
|
// 5. Add fields to the template
|
|
// Add TEXT field
|
|
const textField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.TEXT,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 5,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'text',
|
|
label: 'Default Text Field',
|
|
},
|
|
},
|
|
});
|
|
|
|
// Add NUMBER field
|
|
const numberField = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.NUMBER,
|
|
page: 1,
|
|
positionX: 5,
|
|
positionY: 15,
|
|
width: 20,
|
|
height: 5,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'number',
|
|
label: 'Default Number Field',
|
|
},
|
|
},
|
|
});
|
|
|
|
// 6. Sign in as the user
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
});
|
|
|
|
// 7. Navigate to the template
|
|
await page.goto(`${WEBAPP_BASE_URL}/templates/${template.id}`);
|
|
|
|
// 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`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
templateId: template.id,
|
|
recipients: [
|
|
{
|
|
id: recipient.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
expect(response.ok()).toBeTruthy();
|
|
expect(response.status()).toBe(200);
|
|
|
|
expect(responseData.id).toBeDefined();
|
|
|
|
// 9. Verify the document was created with default fields
|
|
const document = await prisma.document.findUnique({
|
|
where: {
|
|
id: responseData.id,
|
|
},
|
|
include: {
|
|
fields: true,
|
|
},
|
|
});
|
|
|
|
expect(document).not.toBeNull();
|
|
|
|
// 10. Verify fields have their default values
|
|
const documentTextField = document?.fields.find((field) => field.type === FieldType.TEXT);
|
|
expect(documentTextField?.fieldMeta).toMatchObject({
|
|
type: 'text',
|
|
label: 'Default Text Field',
|
|
});
|
|
|
|
const documentNumberField = document?.fields.find((field) => field.type === FieldType.NUMBER);
|
|
expect(documentNumberField?.fieldMeta).toMatchObject({
|
|
type: 'number',
|
|
label: 'Default Number Field',
|
|
});
|
|
|
|
const sendResponse = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/document/distribute`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
documentId: document?.id,
|
|
meta: {
|
|
subject: 'Test Subject',
|
|
message: 'Test Message',
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(sendResponse.ok()).toBeTruthy();
|
|
await expect(sendResponse.status()).toBe(200);
|
|
|
|
// 11. Sign in as the recipient and verify the default fields are visible
|
|
const documentRecipient = await prisma.recipient.findFirst({
|
|
where: {
|
|
documentId: document?.id,
|
|
email: 'recipient@example.com',
|
|
},
|
|
});
|
|
|
|
expect(documentRecipient).not.toBeNull();
|
|
|
|
// Visit the signing page
|
|
await page.goto(`${WEBAPP_BASE_URL}/sign/${documentRecipient?.token}`);
|
|
|
|
await expect(page.getByText('This is prefilled')).not.toBeVisible();
|
|
});
|
|
|
|
test('should handle invalid field prefill values', async ({ request }) => {
|
|
// 1. Create a user
|
|
const user = await seedUser();
|
|
|
|
// 2. Create an API token for the user
|
|
const { token } = await createApiToken({
|
|
userId: user.id,
|
|
tokenName: 'test-token',
|
|
expiresIn: null,
|
|
});
|
|
|
|
// 3. Create a template using seedBlankTemplate
|
|
const template = await seedBlankTemplate(user, {
|
|
createTemplateOptions: {
|
|
title: 'Template for Invalid Test V2',
|
|
visibility: 'EVERYONE',
|
|
},
|
|
});
|
|
|
|
// 4. Create a recipient for the template
|
|
const recipient = await prisma.recipient.create({
|
|
data: {
|
|
templateId: template.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
role: RecipientRole.SIGNER,
|
|
token: 'test-token',
|
|
readStatus: 'NOT_OPENED',
|
|
sendStatus: 'NOT_SENT',
|
|
signingStatus: 'NOT_SIGNED',
|
|
},
|
|
});
|
|
|
|
// 5. Add a field to the template
|
|
const field = await prisma.field.create({
|
|
data: {
|
|
templateId: template.id,
|
|
recipientId: recipient.id,
|
|
type: FieldType.RADIO,
|
|
page: 1,
|
|
positionX: 100,
|
|
positionY: 100,
|
|
width: 100,
|
|
height: 50,
|
|
customText: '',
|
|
inserted: false,
|
|
fieldMeta: {
|
|
type: 'radio',
|
|
label: 'Radio Field',
|
|
values: [
|
|
{ id: 1, value: 'Option A', checked: false },
|
|
{ id: 2, value: 'Option B', checked: false },
|
|
],
|
|
},
|
|
},
|
|
});
|
|
|
|
// 7. Try to create a document with invalid prefill value
|
|
const response = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
data: {
|
|
templateId: template.id,
|
|
recipients: [
|
|
{
|
|
id: recipient.id,
|
|
email: 'recipient@example.com',
|
|
name: 'Test Recipient',
|
|
},
|
|
],
|
|
prefillFields: [
|
|
{
|
|
id: field.id,
|
|
type: 'radio',
|
|
label: 'Invalid Radio',
|
|
value: 'Non-existent Option', // This option doesn't exist
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// 8. Verify the request fails with appropriate error
|
|
expect(response.ok()).toBeFalsy();
|
|
expect(response.status()).toBe(400);
|
|
|
|
const errorData = await response.json();
|
|
expect(errorData.message).toContain('not found in options for RADIO field');
|
|
});
|
|
});
|