- New l4_proxy_hosts table and Drizzle migration (0015) - Full CRUD model layer with validation, audit logging, and Caddy config generation (buildL4Servers integrating into buildCaddyDocument) - Server actions, paginated list page, create/edit/delete dialogs - L4 port manager sidecar (docker/l4-port-manager) that auto-recreates the caddy container when port mappings change via a trigger file - Auto-detects Docker Compose project name from caddy container labels - Supports both named-volume and bind-mount (COMPOSE_HOST_DIR) deployments - getL4PortsStatus simplified: status file is sole source of truth, trigger files deleted after processing to prevent stuck 'Waiting' banner - Navigation entry added (CableIcon) - Tests: unit (entrypoint.sh invariants + validation), integration (ports lifecycle + caddy config), E2E (CRUD + functional routing) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
69 lines
2.8 KiB
TypeScript
69 lines
2.8 KiB
TypeScript
/**
|
|
* E2E tests: L4 Proxy Hosts page.
|
|
*
|
|
* Verifies the L4 Proxy Hosts UI — navigation, list, create/edit/delete dialogs.
|
|
*/
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('L4 Proxy Hosts page', () => {
|
|
test('is accessible from sidebar navigation', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByRole('link', { name: /l4 proxy hosts/i }).click();
|
|
await expect(page).toHaveURL(/\/l4-proxy-hosts/);
|
|
await expect(page.getByText('L4 Proxy Hosts')).toBeVisible();
|
|
});
|
|
|
|
test('shows empty state when no L4 hosts exist', async ({ page }) => {
|
|
await page.goto('/l4-proxy-hosts');
|
|
await expect(page.getByText(/no l4 proxy hosts found/i)).toBeVisible();
|
|
});
|
|
|
|
test('create dialog opens and contains expected fields', async ({ page }) => {
|
|
await page.goto('/l4-proxy-hosts');
|
|
await page.getByRole('button', { name: /create l4 host/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
// Verify key form fields exist
|
|
await expect(page.getByLabel('Name')).toBeVisible();
|
|
await expect(page.getByLabel('Protocol')).toBeVisible();
|
|
await expect(page.getByLabel('Listen Address')).toBeVisible();
|
|
await expect(page.getByLabel('Upstreams')).toBeVisible();
|
|
await expect(page.getByLabel('Matcher')).toBeVisible();
|
|
});
|
|
|
|
test('creates a new L4 proxy host', async ({ page }) => {
|
|
await page.goto('/l4-proxy-hosts');
|
|
await page.getByRole('button', { name: /create l4 host/i }).click();
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
|
|
await page.getByLabel('Name').fill('E2E Test Host');
|
|
await page.getByLabel('Listen Address').fill(':19999');
|
|
await page.getByLabel('Upstreams').fill('10.0.0.1:5432');
|
|
|
|
await page.getByRole('button', { name: /create/i }).click();
|
|
|
|
// Dialog should close and host should appear in table
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10_000 });
|
|
await expect(page.getByText('E2E Test Host')).toBeVisible();
|
|
await expect(page.getByText(':19999')).toBeVisible();
|
|
});
|
|
|
|
test('deletes the created L4 proxy host', async ({ page }) => {
|
|
await page.goto('/l4-proxy-hosts');
|
|
await expect(page.getByText('E2E Test Host')).toBeVisible();
|
|
|
|
// Click delete button in the row
|
|
const row = page.locator('tr', { hasText: 'E2E Test Host' });
|
|
await row.getByRole('button', { name: /delete/i }).click();
|
|
|
|
// Confirm deletion
|
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
await expect(page.getByText(/are you sure/i)).toBeVisible();
|
|
await page.getByRole('button', { name: /delete/i }).click();
|
|
|
|
// Host should be removed
|
|
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10_000 });
|
|
await expect(page.getByText('E2E Test Host')).not.toBeVisible({ timeout: 5_000 });
|
|
});
|
|
});
|