Files
Charon/tests/security-enforcement/acl-dropdown-regression.spec.ts
akanealw eec8c28fb3
Some checks are pending
Go Benchmark / Performance Regression Check (push) Waiting to run
Cerberus Integration / Cerberus Security Stack Integration (push) Waiting to run
Upload Coverage to Codecov / Backend Codecov Upload (push) Waiting to run
Upload Coverage to Codecov / Frontend Codecov Upload (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (go) (push) Waiting to run
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Waiting to run
CrowdSec Integration / CrowdSec Bouncer Integration (push) Waiting to run
Docker Build, Publish & Test / build-and-push (push) Waiting to run
Docker Build, Publish & Test / Security Scan PR Image (push) Blocked by required conditions
Quality Checks / Auth Route Protection Contract (push) Waiting to run
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Waiting to run
Quality Checks / Backend (Go) (push) Waiting to run
Quality Checks / Frontend (React) (push) Waiting to run
Rate Limit integration / Rate Limiting Integration (push) Waiting to run
Security Scan (PR) / Trivy Binary Scan (push) Waiting to run
Supply Chain Verification (PR) / Verify Supply Chain (push) Waiting to run
WAF integration / Coraza WAF Integration (push) Waiting to run
changed perms
2026-04-22 18:19:14 +00:00

187 lines
7.6 KiB
TypeScript
Executable File

import { test, expect } from '@playwright/test';
type SelectionPair = {
aclLabel: string;
securityHeadersLabel: string;
};
async function dismissDomainDialog(page: import('@playwright/test').Page): Promise<void> {
const noThanksButton = page.getByRole('button', { name: /no, thanks/i });
if (await noThanksButton.isVisible({ timeout: 1200 }).catch(() => false)) {
await noThanksButton.click();
}
}
async function openCreateModal(page: import('@playwright/test').Page): Promise<void> {
const addButton = page.getByRole('button', { name: /add.*proxy.*host|create/i }).first();
await expect(addButton).toBeEnabled();
await addButton.click();
await expect(page.getByRole('dialog')).toBeVisible();
}
async function selectFirstUsableOption(
page: import('@playwright/test').Page,
trigger: import('@playwright/test').Locator,
skipPattern: RegExp
): Promise<string> {
await trigger.click();
const listbox = page.getByRole('listbox');
await expect(listbox).toBeVisible();
const options = listbox.getByRole('option');
const optionCount = await options.count();
expect(optionCount).toBeGreaterThan(0);
for (let i = 0; i < optionCount; i++) {
const option = options.nth(i);
const rawLabel = (await option.textContent())?.trim() || '';
const isDisabled = (await option.getAttribute('aria-disabled')) === 'true';
if (isDisabled || !rawLabel || skipPattern.test(rawLabel)) {
continue;
}
await option.click();
return rawLabel;
}
throw new Error('No selectable non-default option found in dropdown');
}
async function selectOptionByName(
page: import('@playwright/test').Page,
trigger: import('@playwright/test').Locator,
optionName: RegExp
): Promise<string> {
await trigger.click();
const listbox = page.getByRole('listbox');
await expect(listbox).toBeVisible();
const option = listbox.getByRole('option', { name: optionName }).first();
await expect(option).toBeVisible();
const label = ((await option.textContent()) || '').trim();
await option.click();
return label;
}
async function saveProxyHost(page: import('@playwright/test').Page): Promise<void> {
await dismissDomainDialog(page);
const saveButton = page
.getByTestId('proxy-host-save')
.or(page.getByRole('button', { name: /^save$/i }))
.first();
await expect(saveButton).toBeEnabled();
await saveButton.click();
const confirmSave = page.getByRole('button', { name: /yes.*save/i }).first();
if (await confirmSave.isVisible({ timeout: 1200 }).catch(() => false)) {
await confirmSave.click();
}
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
}
async function openEditModalForDomain(page: import('@playwright/test').Page, domain: string): Promise<void> {
const row = page.locator('tbody tr').filter({ hasText: domain }).first();
await expect(row).toBeVisible({ timeout: 10000 });
const editButton = row.getByRole('button', { name: /edit proxy host|edit/i }).first();
await expect(editButton).toBeVisible();
await editButton.click();
await expect(page.getByRole('dialog')).toBeVisible();
}
async function selectNonDefaultPair(
page: import('@playwright/test').Page,
dialog: import('@playwright/test').Locator
): Promise<SelectionPair> {
const aclTrigger = dialog.getByRole('combobox', { name: /access control list/i });
const securityHeadersTrigger = dialog.getByRole('combobox', { name: /security headers/i });
const aclLabel = await selectFirstUsableOption(page, aclTrigger, /no access control|public/i);
await expect(aclTrigger).toContainText(aclLabel);
const securityHeadersLabel = await selectFirstUsableOption(page, securityHeadersTrigger, /none \(no security headers\)/i);
await expect(securityHeadersTrigger).toContainText(securityHeadersLabel);
return { aclLabel, securityHeadersLabel };
}
test.describe('ProxyHostForm ACL and Security Headers Dropdown Regression', () => {
test('should keep ACL and Security Headers behavior equivalent across create/edit flows', async ({ page }) => {
const suffix = Date.now();
const proxyName = `Dropdown Regression ${suffix}`;
const proxyDomain = `dropdown-${suffix}.test.local`;
await test.step('Navigate to Proxy Hosts', async () => {
await page.goto('/proxy-hosts');
await page.waitForLoadState('networkidle');
await expect(page.getByRole('heading', { name: /proxy hosts/i }).first()).toBeVisible();
});
await test.step('Create flow: select ACL + Security Headers and verify immediate form state', async () => {
await openCreateModal(page);
const dialog = page.getByRole('dialog');
await dialog.locator('#proxy-name').fill(proxyName);
await dialog.locator('#domain-names').click();
await page.keyboard.type(proxyDomain);
await page.keyboard.press('Tab');
await dismissDomainDialog(page);
await dialog.locator('#forward-host').fill('127.0.0.1');
await dialog.locator('#forward-port').fill('8080');
const initialSelection = await selectNonDefaultPair(page, dialog);
await saveProxyHost(page);
await openEditModalForDomain(page, proxyDomain);
const reopenDialog = page.getByRole('dialog');
await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(initialSelection.aclLabel);
await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(initialSelection.securityHeadersLabel);
await reopenDialog.getByRole('button', { name: /cancel/i }).click();
await expect(reopenDialog).not.toBeVisible({ timeout: 5000 });
});
await test.step('Edit flow: change ACL + Security Headers and verify persisted updates', async () => {
await openEditModalForDomain(page, proxyDomain);
const dialog = page.getByRole('dialog');
const updatedSelection = await selectNonDefaultPair(page, dialog);
await saveProxyHost(page);
await openEditModalForDomain(page, proxyDomain);
const reopenDialog = page.getByRole('dialog');
await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(updatedSelection.aclLabel);
await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(updatedSelection.securityHeadersLabel);
await reopenDialog.getByRole('button', { name: /cancel/i }).click();
await expect(reopenDialog).not.toBeVisible({ timeout: 5000 });
});
await test.step('Edit flow: clear both to none/null and verify persisted clearing', async () => {
await openEditModalForDomain(page, proxyDomain);
const dialog = page.getByRole('dialog');
const aclTrigger = dialog.getByRole('combobox', { name: /access control list/i });
const securityHeadersTrigger = dialog.getByRole('combobox', { name: /security headers/i });
const aclNoneLabel = await selectOptionByName(page, aclTrigger, /no access control \(public\)/i);
await expect(aclTrigger).toContainText(aclNoneLabel);
const securityNoneLabel = await selectOptionByName(page, securityHeadersTrigger, /none \(no security headers\)/i);
await expect(securityHeadersTrigger).toContainText(securityNoneLabel);
await saveProxyHost(page);
await openEditModalForDomain(page, proxyDomain);
const reopenDialog = page.getByRole('dialog');
await expect(reopenDialog.getByRole('combobox', { name: /access control list/i })).toContainText(/no access control \(public\)/i);
await expect(reopenDialog.getByRole('combobox', { name: /security headers/i })).toContainText(/none \(no security headers\)/i);
await reopenDialog.getByRole('button', { name: /cancel/i }).click();
await expect(reopenDialog).not.toBeVisible({ timeout: 5000 });
});
});
});