296 lines
9.3 KiB
TypeScript
296 lines
9.3 KiB
TypeScript
/**
|
|
* Webhook Management E2E Tests
|
|
*
|
|
* Tests for validating webhook CRUD operations and delivery history
|
|
* through the frontend-to-backend Tauri IPC pipeline.
|
|
*/
|
|
|
|
import { expect, test } from '@playwright/test';
|
|
import {
|
|
callAPI,
|
|
closeDialog,
|
|
createTestWebhook,
|
|
generateTestId,
|
|
navigateTo,
|
|
TEST_DATA,
|
|
waitForAPI,
|
|
waitForLoadingComplete,
|
|
} from './fixtures';
|
|
|
|
const shouldRun = process.env.NOTEFLOW_E2E === '1';
|
|
|
|
interface Webhook {
|
|
id: string;
|
|
name: string;
|
|
url: string;
|
|
events: string[];
|
|
enabled: boolean;
|
|
timeout_ms: number;
|
|
max_retries: number;
|
|
created_at: number;
|
|
updated_at: number;
|
|
}
|
|
|
|
test.describe('webhook api integration', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.');
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
});
|
|
|
|
test('registerWebhook creates a new webhook', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const webhook = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
expect(webhook).toHaveProperty('id');
|
|
expect(webhook.name).toBe(testData.name);
|
|
expect(webhook.url).toBe(testData.url);
|
|
expect(webhook.events).toEqual(testData.events);
|
|
expect(webhook.enabled).toBe(true);
|
|
expect(typeof webhook.created_at).toBe('number');
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', webhook.id);
|
|
});
|
|
|
|
test('listWebhooks returns array of webhooks', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const created = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
const result = await callAPI<{ webhooks: Webhook[]; total_count: number }>(
|
|
page,
|
|
'listWebhooks'
|
|
);
|
|
|
|
expect(result).toHaveProperty('webhooks');
|
|
expect(result).toHaveProperty('total_count');
|
|
expect(Array.isArray(result.webhooks)).toBe(true);
|
|
expect(result.webhooks.some((w) => w.id === created.id)).toBe(true);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', created.id);
|
|
});
|
|
|
|
test('listWebhooks supports enabledOnly filter', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const enabled = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
const enabledResult = await callAPI<{ webhooks: Webhook[] }>(page, 'listWebhooks', true);
|
|
|
|
for (const webhook of enabledResult.webhooks) {
|
|
expect(webhook.enabled).toBe(true);
|
|
}
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', enabled.id);
|
|
});
|
|
|
|
test('updateWebhook modifies webhook configuration', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const created = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
const newName = `Updated ${generateTestId()}`;
|
|
const newUrl = 'https://example.com/updated-webhook';
|
|
const updated = await callAPI<Webhook>(page, 'updateWebhook', {
|
|
webhook_id: created.id,
|
|
name: newName,
|
|
url: newUrl,
|
|
events: ['summary.generated'],
|
|
enabled: false,
|
|
});
|
|
|
|
expect(updated.name).toBe(newName);
|
|
expect(updated.url).toBe(newUrl);
|
|
expect(updated.events).toEqual(['summary.generated']);
|
|
expect(updated.enabled).toBe(false);
|
|
expect(updated.updated_at).toBeGreaterThanOrEqual(created.updated_at);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', created.id);
|
|
});
|
|
|
|
test('deleteWebhook removes a webhook', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const created = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
const deleted = await callAPI<{ success: boolean }>(page, 'deleteWebhook', created.id);
|
|
expect(deleted.success).toBe(true);
|
|
|
|
const listResult = await callAPI<{ webhooks: Webhook[] }>(page, 'listWebhooks');
|
|
const stillExists = listResult.webhooks.some((w) => w.id === created.id);
|
|
expect(stillExists).toBe(false);
|
|
});
|
|
|
|
test('getWebhookDeliveries returns delivery history', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const created = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
const deliveries = await callAPI<{ deliveries: unknown[]; total_count: number }>(
|
|
page,
|
|
'getWebhookDeliveries',
|
|
created.id,
|
|
50
|
|
);
|
|
|
|
expect(deliveries).toHaveProperty('deliveries');
|
|
expect(deliveries).toHaveProperty('total_count');
|
|
expect(Array.isArray(deliveries.deliveries)).toBe(true);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', created.id);
|
|
});
|
|
|
|
test('webhook events array accepts all valid event types', async ({ page }) => {
|
|
const testData = createTestWebhook({
|
|
events: ['meeting.completed', 'summary.generated', 'recording.started', 'recording.stopped'],
|
|
});
|
|
|
|
const webhook = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
expect(webhook.events).toHaveLength(4);
|
|
expect(webhook.events).toContain('meeting.completed');
|
|
expect(webhook.events).toContain('summary.generated');
|
|
expect(webhook.events).toContain('recording.started');
|
|
expect(webhook.events).toContain('recording.stopped');
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', webhook.id);
|
|
});
|
|
|
|
test('webhook respects timeout and retry configuration', async ({ page }) => {
|
|
const testData = createTestWebhook();
|
|
const webhook = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: ['meeting.completed'],
|
|
name: testData.name,
|
|
timeout_ms: 5000,
|
|
max_retries: 5,
|
|
});
|
|
|
|
expect(webhook.timeout_ms).toBe(5000);
|
|
expect(webhook.max_retries).toBe(5);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', webhook.id);
|
|
});
|
|
});
|
|
|
|
test.describe('webhook settings ui', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.');
|
|
|
|
test('settings page displays webhook section', async ({ page }) => {
|
|
await navigateTo(page, '/settings?tab=integrations');
|
|
await waitForLoadingComplete(page);
|
|
|
|
const mainContent = page.locator('main');
|
|
await expect(mainContent).toBeVisible();
|
|
});
|
|
|
|
test('add webhook button opens dialog', async ({ page }) => {
|
|
await navigateTo(page, '/settings?tab=integrations');
|
|
await waitForLoadingComplete(page);
|
|
|
|
const addButton = page.locator('button:has-text("Add Webhook")');
|
|
if (await addButton.isVisible()) {
|
|
await addButton.click();
|
|
|
|
const dialog = page.locator('[role="dialog"]');
|
|
await expect(dialog).toBeVisible();
|
|
|
|
await closeDialog(page);
|
|
}
|
|
});
|
|
|
|
test('webhook list shows registered webhooks', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const testData = createTestWebhook();
|
|
const created = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
await navigateTo(page, '/settings?tab=integrations');
|
|
await waitForLoadingComplete(page);
|
|
|
|
// Clean up
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', created.id);
|
|
});
|
|
});
|
|
|
|
test.describe('webhook data validation', () => {
|
|
test.skip(!shouldRun, 'Set NOTEFLOW_E2E=1 to enable end-to-end tests.');
|
|
|
|
test('webhook has correct timestamp formats', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const testData = createTestWebhook();
|
|
const webhook = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
expect(webhook.created_at.toString().length).toBeLessThanOrEqual(10);
|
|
expect(webhook.updated_at.toString().length).toBeLessThanOrEqual(10);
|
|
|
|
const minTimestamp = 1704067200;
|
|
expect(webhook.created_at).toBeGreaterThan(minTimestamp);
|
|
expect(webhook.updated_at).toBeGreaterThan(minTimestamp);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', webhook.id);
|
|
});
|
|
|
|
test('webhook id is valid uuid format', async ({ page }) => {
|
|
await navigateTo(page, '/');
|
|
await waitForAPI(page);
|
|
|
|
const testData = createTestWebhook();
|
|
const webhook = await callAPI<Webhook>(page, 'registerWebhook', {
|
|
workspace_id: TEST_DATA.DEFAULT_WORKSPACE_ID,
|
|
url: testData.url,
|
|
events: testData.events,
|
|
name: testData.name,
|
|
});
|
|
|
|
expect(webhook.id).toMatch(/^[a-f0-9-]{8,}$/i);
|
|
|
|
await callAPI<{ success: boolean }>(page, 'deleteWebhook', webhook.id);
|
|
});
|
|
});
|