mirror of
https://github.com/hanzoai/sign.git
synced 2026-10-30 14:57:59 +00:00
Emails, the signing page's title, the sealed PDF's reason, the sign-in refusal and the dialogs still said Hanzo Sign. The catalogs change with the strings, message for message, so every translation keeps its id.
383 lines
13 KiB
TypeScript
383 lines
13 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
import { WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
|
|
|
import { NEXT_PUBLIC_WEBAPP_URL } from '@hanzo/esign-lib/constants/app';
|
|
import { alphaid } from '@hanzo/esign-lib/universal/id';
|
|
import { prisma } from '@hanzo/esign-prisma';
|
|
import { seedBlankDocument } from '@hanzo/esign-prisma/seed/documents';
|
|
import { seedUser } from '@hanzo/esign-prisma/seed/users';
|
|
|
|
import { apiSignin, apiSignout } from '../fixtures/authentication';
|
|
import { expectTextToBeVisible, openDropdownMenu } from '../fixtures/generic';
|
|
|
|
/**
|
|
* Helper function to seed a webhook directly in the database for testing.
|
|
*/
|
|
const seedWebhook = async ({
|
|
webhookUrl,
|
|
eventTriggers,
|
|
secret,
|
|
enabled,
|
|
userId,
|
|
teamId,
|
|
}: {
|
|
webhookUrl: string;
|
|
eventTriggers: WebhookTriggerEvents[];
|
|
secret?: string | null;
|
|
enabled?: boolean;
|
|
userId: number;
|
|
teamId: number;
|
|
}) => {
|
|
return await prisma.webhook.create({
|
|
data: {
|
|
webhookUrl,
|
|
eventTriggers,
|
|
secret: secret ?? null,
|
|
enabled: enabled ?? true,
|
|
userId,
|
|
teamId,
|
|
},
|
|
});
|
|
};
|
|
|
|
test('[WEBHOOKS]: create webhook', async ({ page }) => {
|
|
const { user, team } = await seedUser();
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: `/t/${team.url}/settings/webhooks`,
|
|
});
|
|
|
|
const webhookUrl = `https://example.com/webhook-${Date.now()}`;
|
|
|
|
// Click Create Webhook button
|
|
await page.getByRole('button', { name: 'Create Webhook' }).click();
|
|
|
|
// Fill in the form
|
|
await page.getByLabel('Webhook URL*').fill(webhookUrl);
|
|
|
|
// Select event trigger - click on the triggers field and select DOCUMENT_CREATED
|
|
await page.getByLabel('Triggers').click();
|
|
await page.waitForTimeout(200); // Wait for dropdown to open
|
|
await page.getByText('document.created').click();
|
|
|
|
// Click outside the triggers field to close the dropdown
|
|
await page.getByText('The URL for Hanzo eSign to send webhook events to.').click();
|
|
|
|
// Fill in the form
|
|
await page.getByLabel('Secret').fill('secret');
|
|
|
|
// Submit the form
|
|
await page.getByRole('button', { name: 'Create' }).click();
|
|
|
|
// Wait for success toast
|
|
await expectTextToBeVisible(page, 'Webhook created');
|
|
await expectTextToBeVisible(page, 'The webhook was successfully created.');
|
|
|
|
// Verify webhook appears in the list
|
|
await expect(page.getByText(webhookUrl)).toBeVisible();
|
|
|
|
// Directly check database
|
|
const dbWebhook = await prisma.webhook.findFirstOrThrow({
|
|
where: {
|
|
userId: user.id,
|
|
},
|
|
});
|
|
|
|
expect(dbWebhook?.eventTriggers).toEqual([WebhookTriggerEvents.DOCUMENT_CREATED]);
|
|
expect(dbWebhook?.secret).toBe('secret');
|
|
expect(dbWebhook?.enabled).toBe(true);
|
|
});
|
|
|
|
test('[WEBHOOKS]: view webhooks', async ({ page }) => {
|
|
const { user, team } = await seedUser();
|
|
|
|
const webhookUrl = `https://example.com/webhook-${Date.now()}`;
|
|
|
|
// Create a webhook via seeding
|
|
const webhook = await seedWebhook({
|
|
webhookUrl,
|
|
eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED, WebhookTriggerEvents.DOCUMENT_SENT],
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
enabled: true,
|
|
});
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: `/t/${team.url}/settings/webhooks`,
|
|
});
|
|
|
|
// Verify webhook is visible in the table
|
|
await expect(page.getByText(webhookUrl)).toBeVisible();
|
|
await expect(page.getByText('Enabled')).toBeVisible();
|
|
await expect(page.getByText('2 Events')).toBeVisible();
|
|
|
|
// Click on webhook to navigate to detail page
|
|
await page.getByText(webhookUrl).click();
|
|
|
|
// Verify detail page shows webhook information
|
|
await page.waitForURL(`/t/${team.url}/settings/webhooks/${webhook.id}`);
|
|
await expect(page.getByText(webhookUrl)).toBeVisible();
|
|
await expect(page.getByText('Enabled')).toBeVisible();
|
|
});
|
|
|
|
test('[WEBHOOKS]: delete webhook', async ({ page }) => {
|
|
const { user, team } = await seedUser();
|
|
|
|
const webhookUrl = `https://example.com/webhook-${Date.now()}`;
|
|
|
|
// Create a webhook via seeding
|
|
const webhook = await seedWebhook({
|
|
webhookUrl,
|
|
eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED],
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
});
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: `/t/${team.url}/settings/webhooks`,
|
|
});
|
|
|
|
// Verify webhook is visible
|
|
await expect(page.getByText(webhookUrl)).toBeVisible();
|
|
|
|
// Find the row with the webhook and click the action dropdown
|
|
const webhookRow = page.locator('tr', { hasText: webhookUrl });
|
|
const actionBtn = webhookRow.getByTestId('webhook-table-action-btn');
|
|
await openDropdownMenu(page, actionBtn);
|
|
|
|
// Click Delete menu item
|
|
await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible();
|
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
|
|
|
// Fill in confirmation field
|
|
const deleteMessage = `delete ${webhookUrl}`;
|
|
// The label contains "Confirm by typing:" followed by the delete message
|
|
await page.getByLabel(/Confirm by typing/).fill(deleteMessage);
|
|
|
|
// Click delete button
|
|
await page.getByRole('button', { name: 'Delete' }).click();
|
|
|
|
// Wait for success toast
|
|
await expectTextToBeVisible(page, 'Webhook deleted');
|
|
await expectTextToBeVisible(page, 'The webhook has been successfully deleted.');
|
|
|
|
// Verify webhook is removed from the list
|
|
await expect(page.getByText(webhookUrl)).not.toBeVisible();
|
|
});
|
|
|
|
test('[WEBHOOKS]: update webhook', async ({ page }) => {
|
|
const { user, team } = await seedUser();
|
|
|
|
const originalWebhookUrl = `https://example.com/webhook-original-${Date.now()}`;
|
|
const updatedWebhookUrl = `https://example.com/webhook-updated-${Date.now()}`;
|
|
|
|
// Create a webhook via seeding with initial values
|
|
const webhook = await seedWebhook({
|
|
webhookUrl: originalWebhookUrl,
|
|
eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED, WebhookTriggerEvents.DOCUMENT_SENT],
|
|
userId: user.id,
|
|
teamId: team.id,
|
|
enabled: true,
|
|
});
|
|
|
|
await apiSignin({
|
|
page,
|
|
email: user.email,
|
|
redirectPath: `/t/${team.url}/settings/webhooks`,
|
|
});
|
|
|
|
// Verify webhook is visible with original values
|
|
await expect(page.getByText(originalWebhookUrl)).toBeVisible();
|
|
await expect(page.getByText('Enabled')).toBeVisible();
|
|
await expect(page.getByText('2 Events')).toBeVisible();
|
|
|
|
// Find the row with the webhook and click the action dropdown
|
|
const webhookRow = page.locator('tr', { hasText: originalWebhookUrl });
|
|
const actionBtn = webhookRow.getByTestId('webhook-table-action-btn');
|
|
await openDropdownMenu(page, actionBtn);
|
|
|
|
// Click Edit menu item
|
|
await expect(page.getByRole('menuitem', { name: 'Edit' })).toBeVisible();
|
|
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
|
|
|
// Wait for dialog to open
|
|
await page.waitForTimeout(200);
|
|
|
|
// Change the webhook URL
|
|
await page.getByLabel('Webhook URL').clear();
|
|
await page.getByLabel('Webhook URL').fill(updatedWebhookUrl);
|
|
|
|
// Disable the webhook (toggle the switch)
|
|
const enabledSwitch = page.getByLabel('Enabled');
|
|
const isChecked = await enabledSwitch.isChecked();
|
|
if (isChecked) {
|
|
await enabledSwitch.click();
|
|
}
|
|
|
|
// Change the event triggers - remove one existing event and add a new one
|
|
// The selected items are shown as badges with remove buttons
|
|
// Remove one of the existing events (DOCUMENT_SENT) by clicking its remove button
|
|
const removeButtons = page.locator('button[aria-label="Remove"]');
|
|
const removeButtonCount = await removeButtons.count();
|
|
|
|
// Remove the "DOCUMENT_SENT" event (this will remove one of the two)
|
|
if (removeButtonCount > 0) {
|
|
await removeButtons.nth(1).click();
|
|
await page.waitForTimeout(200);
|
|
}
|
|
|
|
// Add new event triggers
|
|
await page.getByLabel('Triggers').click();
|
|
await page.waitForTimeout(200);
|
|
|
|
// Select DOCUMENT_COMPLETED (this will be added to the remaining DOCUMENT_CREATED)
|
|
await page.getByText('document.completed').click();
|
|
await page.waitForTimeout(200);
|
|
|
|
// Click outside to close the dropdown
|
|
await page.getByText('The URL for Hanzo eSign to send webhook events to.').click();
|
|
|
|
// Submit the form
|
|
await page.getByRole('button', { name: 'Update' }).click();
|
|
|
|
// Wait for success toast
|
|
await expectTextToBeVisible(page, 'Webhook updated');
|
|
await expectTextToBeVisible(page, 'The webhook has been updated successfully.');
|
|
|
|
// Verify changes are reflected in the list
|
|
// The old URL should be gone and new URL should be visible
|
|
await expect(page.getByText(originalWebhookUrl)).not.toBeVisible();
|
|
await expect(page.getByText(updatedWebhookUrl)).toBeVisible();
|
|
// Verify webhook is disabled
|
|
await expect(page.getByText('Disabled')).toBeVisible();
|
|
// Verify event count is still 2 (one removed, one added - DOCUMENT_CREATED and DOCUMENT_COMPLETED)
|
|
await expect(page.getByText('2 Events')).toBeVisible();
|
|
|
|
// Check the database directly to verify
|
|
const dbWebhook = await prisma.webhook.findUnique({
|
|
where: {
|
|
id: webhook.id,
|
|
},
|
|
});
|
|
|
|
expect(dbWebhook?.eventTriggers).toEqual([
|
|
WebhookTriggerEvents.DOCUMENT_CREATED,
|
|
WebhookTriggerEvents.DOCUMENT_COMPLETED,
|
|
]);
|
|
expect(dbWebhook?.enabled).toBe(false);
|
|
expect(dbWebhook?.webhookUrl).toBe(updatedWebhookUrl);
|
|
expect(dbWebhook?.secret).toBe('');
|
|
});
|
|
|
|
test('[WEBHOOKS]: cannot see unrelated webhooks', async ({ page }) => {
|
|
// Create two separate users with teams
|
|
const user1Data = await seedUser();
|
|
const user2Data = await seedUser();
|
|
|
|
const webhookUrl1 = `https://example.com/webhook-team1-${alphaid(12)}`;
|
|
const webhookUrl2 = `https://example.com/webhook-team2-${alphaid(12)}`;
|
|
|
|
// Create webhooks for both teams with DOCUMENT_CREATED event
|
|
const webhook1 = await seedWebhook({
|
|
webhookUrl: webhookUrl1,
|
|
eventTriggers: [WebhookTriggerEvents.DOCUMENT_CREATED],
|
|
userId: user1Data.user.id,
|
|
teamId: user1Data.team.id,
|
|
enabled: true,
|
|
});
|
|
|
|
const webhook2 = await seedWebhook({
|
|
webhookUrl: webhookUrl2,
|
|
eventTriggers: [WebhookTriggerEvents.DOCUMENT_SENT],
|
|
userId: user2Data.user.id,
|
|
teamId: user2Data.team.id,
|
|
});
|
|
|
|
// Create a document on team1 to trigger the webhook
|
|
const document = await seedBlankDocument(user1Data.user, user1Data.team.id, {
|
|
createDocumentOptions: {
|
|
title: 'Test Document for Webhook',
|
|
},
|
|
});
|
|
|
|
// Create a webhook call for team1's webhook (simulating the webhook being triggered)
|
|
// Since webhooks are triggered via jobs which may not run in tests, we create the call directly
|
|
const webhookCall1 = await prisma.webhookCall.create({
|
|
data: {
|
|
webhookId: webhook1.id,
|
|
url: webhookUrl1,
|
|
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
|
status: WebhookCallStatus.SUCCESS,
|
|
responseCode: 200,
|
|
requestBody: {
|
|
event: WebhookTriggerEvents.DOCUMENT_CREATED,
|
|
payload: {
|
|
id: document.id,
|
|
title: document.title,
|
|
},
|
|
createdAt: new Date().toISOString(),
|
|
webhookEndpoint: webhookUrl1,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Sign in as user1
|
|
await apiSignin({
|
|
page,
|
|
email: user1Data.user.email,
|
|
redirectPath: `/t/${user1Data.team.url}/settings/webhooks`,
|
|
});
|
|
|
|
// Verify user1 can see their webhook
|
|
await expect(page.getByText(webhookUrl1)).toBeVisible();
|
|
// Verify user1 cannot see user2's webhook
|
|
await expect(page.getByText(webhookUrl2)).not.toBeVisible();
|
|
|
|
// Navigate to team1's webhook logs page
|
|
await page.goto(
|
|
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user1Data.team.url}/settings/webhooks/${webhook1.id}`,
|
|
);
|
|
|
|
// Verify user1 can see their webhook logs
|
|
// The webhook call should be visible in the table
|
|
await expect(page.getByText(webhookCall1.id)).toBeVisible();
|
|
await expect(page.getByText('200')).toBeVisible(); // Response code
|
|
|
|
// Sign out and sign in as user2
|
|
await apiSignout({ page });
|
|
await apiSignin({
|
|
page,
|
|
email: user2Data.user.email,
|
|
redirectPath: `/t/${user2Data.team.url}/settings/webhooks`,
|
|
});
|
|
|
|
// Verify user2 can see their webhook
|
|
await expect(page.getByText(webhookUrl2)).toBeVisible();
|
|
// Verify user2 cannot see user1's webhook
|
|
await expect(page.getByText(webhookUrl1)).not.toBeVisible();
|
|
|
|
// Navigate to team2's webhook logs page
|
|
await page.goto(
|
|
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user2Data.team.url}/settings/webhooks/${webhook2.id}`,
|
|
);
|
|
|
|
// Verify user2 cannot see team1's webhook logs
|
|
// The webhook call from team1 should not be visible
|
|
await expect(page.getByText(webhookCall1.id)).not.toBeVisible();
|
|
|
|
// Attempt to access user1's webhook detail page directly via URL
|
|
await page.goto(
|
|
`${NEXT_PUBLIC_WEBAPP_URL()}/t/${user2Data.team.url}/settings/webhooks/${webhook1.id}`,
|
|
);
|
|
|
|
// Verify access is denied - should show error or redirect
|
|
// Based on the component, it shows a 404 error page
|
|
await expect(page.getByRole('heading', { name: 'Webhook not found' })).toBeVisible();
|
|
});
|