Files
noteflow/client/e2e/webhooks.spec.ts
2026-01-14 23:23:01 -05:00

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);
});
});