diff --git a/tests/e2e/geoblock.spec.ts b/tests/e2e/geoblock.spec.ts index b1e11f95..7cc62609 100644 --- a/tests/e2e/geoblock.spec.ts +++ b/tests/e2e/geoblock.spec.ts @@ -10,9 +10,19 @@ const EMPTY_GEOBLOCK = { response_headers: {}, redirect_url: '', }; +/** + * RFC 5737 TEST-NET ranges — routable nowhere, so they won't block real + * traffic when applied to Caddy during tests (unlike 0.0.0.0/0). + */ +const SAFE_BLOCK_CIDR = '198.51.100.0/24'; // TEST-NET-2 +const SAFE_ALLOW_CIDR = '203.0.113.0/24'; // TEST-NET-3 +const SAFE_BLOCK_CIDR_2 = '192.0.2.0/24'; // TEST-NET-1 +const SAFE_ALLOW_CIDR_2 = '233.252.0.0/24'; // MCAST-TEST-NET + +const API_GEOBLOCK = 'http://localhost:3000/api/v1/settings/geoblock'; + /** * Find the visible text input inside a TagInput component by its hidden input name. - * TagInput renders:
...
*/ function cidrInput(parent: ReturnType extends never ? never : any, name: string) { return parent.locator(`div:has(> input[name="${name}"])`) @@ -21,7 +31,7 @@ function cidrInput(parent: ReturnType extends never ? never test.describe('Geo Blocking — form persistence', () => { async function resetGeoblock(page: any) { - await page.request.put('http://localhost:3000/api/v1/settings/geoblock', { data: EMPTY_GEOBLOCK }); + await page.request.put(API_GEOBLOCK, { data: EMPTY_GEOBLOCK }); } test.beforeEach(async ({ page }) => { @@ -30,7 +40,6 @@ test.describe('Geo Blocking — form persistence', () => { }); test.afterEach(async ({ page }) => { - // Always reset after each test so concurrent workers don't hit block-all rules await resetGeoblock(page); }); @@ -38,6 +47,8 @@ test.describe('Geo Blocking — form persistence', () => { * Regression: Radix Tabs unmount inactive tab content, so only the * currently-visible tab's hidden inputs were submitted. Saving while on the * "Block Rules" tab would wipe all allow rules and vice-versa. + * + * Uses RFC 5737 test ranges to avoid blocking real traffic. */ test('saving block rules does not wipe allow rules', async ({ page }) => { const geoSection = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); @@ -46,33 +57,29 @@ test.describe('Geo Blocking — form persistence', () => { await enableSwitch.click(); } - // ── Switch to Allow tab and add a CIDR ─────────────────────────────── await geoSection.getByRole('tab', { name: /allow rules/i }).click(); const allowInput = cidrInput(geoSection, 'geoblock_allow_cidrs'); - await allowInput.fill('192.168.0.0/16'); + await allowInput.fill(SAFE_ALLOW_CIDR); await allowInput.press('Enter'); - await expect(geoSection.locator('text=192.168.0.0/16')).toBeVisible(); + await expect(geoSection.locator(`text=${SAFE_ALLOW_CIDR}`)).toBeVisible(); - // ── Switch to Block tab and add a CIDR ─────────────────────────────── await geoSection.getByRole('tab', { name: /block rules/i }).click(); const blockInput = cidrInput(geoSection, 'geoblock_block_cidrs'); - await blockInput.fill('0.0.0.0/0'); + await blockInput.fill(SAFE_BLOCK_CIDR); await blockInput.press('Enter'); - await expect(geoSection.locator('text=0.0.0.0/0')).toBeVisible(); + await expect(geoSection.locator(`text=${SAFE_BLOCK_CIDR}`)).toBeVisible(); - // ── Save (on Block tab) ────────────────────────────────────────────── await geoSection.getByRole('button', { name: /save geoblocking settings/i }).click(); await expect(geoSection.locator('text=/saved|success/i')).toBeVisible({ timeout: 10000 }); - // ── Reload and verify both rules survived ──────────────────────────── await page.reload(); const fresh = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); await fresh.getByRole('tab', { name: /block rules/i }).click(); - await expect(fresh.locator('text=0.0.0.0/0')).toBeVisible({ timeout: 5000 }); + await expect(fresh.locator(`text=${SAFE_BLOCK_CIDR}`)).toBeVisible({ timeout: 5000 }); await fresh.getByRole('tab', { name: /allow rules/i }).click(); - await expect(fresh.locator('text=192.168.0.0/16')).toBeVisible({ timeout: 5000 }); + await expect(fresh.locator(`text=${SAFE_ALLOW_CIDR}`)).toBeVisible({ timeout: 5000 }); }); test('saving allow rules does not wipe block rules', async ({ page }) => { @@ -82,33 +89,29 @@ test.describe('Geo Blocking — form persistence', () => { await enableSwitch.click(); } - // ── On Block tab, add a CIDR ───────────────────────────────────────── await geoSection.getByRole('tab', { name: /block rules/i }).click(); const blockInput = cidrInput(geoSection, 'geoblock_block_cidrs'); - await blockInput.fill('10.10.0.0/16'); + await blockInput.fill(SAFE_BLOCK_CIDR_2); await blockInput.press('Enter'); - await expect(geoSection.locator('text=10.10.0.0/16')).toBeVisible(); + await expect(geoSection.locator(`text=${SAFE_BLOCK_CIDR_2}`)).toBeVisible(); - // ── Switch to Allow tab and add a CIDR ─────────────────────────────── await geoSection.getByRole('tab', { name: /allow rules/i }).click(); const allowInput = cidrInput(geoSection, 'geoblock_allow_cidrs'); - await allowInput.fill('172.16.0.0/12'); + await allowInput.fill(SAFE_ALLOW_CIDR_2); await allowInput.press('Enter'); - await expect(geoSection.locator('text=172.16.0.0/12')).toBeVisible(); + await expect(geoSection.locator(`text=${SAFE_ALLOW_CIDR_2}`)).toBeVisible(); - // ── Save (on Allow tab) ────────────────────────────────────────────── await geoSection.getByRole('button', { name: /save geoblocking settings/i }).click(); await expect(geoSection.locator('text=/saved|success/i')).toBeVisible({ timeout: 10000 }); - // ── Reload and verify both rules survived ──────────────────────────── await page.reload(); const fresh = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); await fresh.getByRole('tab', { name: /block rules/i }).click(); - await expect(fresh.locator('text=10.10.0.0/16')).toBeVisible({ timeout: 5000 }); + await expect(fresh.locator(`text=${SAFE_BLOCK_CIDR_2}`)).toBeVisible({ timeout: 5000 }); await fresh.getByRole('tab', { name: /allow rules/i }).click(); - await expect(fresh.locator('text=172.16.0.0/12')).toBeVisible({ timeout: 5000 }); + await expect(fresh.locator(`text=${SAFE_ALLOW_CIDR_2}`)).toBeVisible({ timeout: 5000 }); }); /** @@ -123,48 +126,74 @@ test.describe('Geo Blocking — form persistence', () => { await enableSwitch.click(); } - // ── Open accordion and set redirect URL ────────────────────────────── await geoSection.getByRole('button', { name: /trusted proxies/i }).click(); const redirectInput = geoSection.locator('input[name="geoblock_redirect_url"]'); await expect(redirectInput).toBeVisible(); await redirectInput.fill('https://example.com/blocked'); - // ── Collapse accordion ─────────────────────────────────────────────── await geoSection.getByRole('button', { name: /trusted proxies/i }).click(); - // ── Save ───────────────────────────────────────────────────────────── await geoSection.getByRole('button', { name: /save geoblocking settings/i }).click(); await expect(geoSection.locator('text=/saved|success/i')).toBeVisible({ timeout: 10000 }); - // ── Reload and verify redirect URL is still set ────────────────────── await page.reload(); const fresh = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); await fresh.getByRole('button', { name: /trusted proxies/i }).click(); - const freshRedirectInput = fresh.locator('input[name="geoblock_redirect_url"]'); - await expect(freshRedirectInput).toHaveValue('https://example.com/blocked', { timeout: 5000 }); + await expect(fresh.locator('input[name="geoblock_redirect_url"]')) + .toHaveValue('https://example.com/blocked', { timeout: 5000 }); }); /** - * Tests the LAN Only (RFC1918) preset button. + * Tests the LAN Only (RFC1918) preset — values must survive tab switching. + * This test does NOT save, so no Caddy config is affected. */ - test('LAN Only preset fills RFC1918 allow CIDRs and block-all', async ({ page }) => { + test('LAN Only preset: values survive tab switching', async ({ page }) => { const geoSection = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); const enableSwitch = geoSection.getByRole('switch'); if (!(await enableSwitch.isChecked())) { await enableSwitch.click(); } - // ── Click LAN Only preset ──────────────────────────────────────────── await geoSection.getByRole('button', { name: /lan only/i }).click(); - // ── Verify block tab has 0.0.0.0/0 ────────────────────────────────── - await geoSection.getByRole('tab', { name: /block rules/i }).click(); await expect(geoSection.locator('text=0.0.0.0/0')).toBeVisible(); - // ── Verify allow tab has RFC1918 ranges ────────────────────────────── await geoSection.getByRole('tab', { name: /allow rules/i }).click(); await expect(geoSection.locator('text=10.0.0.0/8')).toBeVisible(); await expect(geoSection.locator('text=172.16.0.0/12')).toBeVisible(); await expect(geoSection.locator('text=192.168.0.0/16')).toBeVisible(); + + await geoSection.getByRole('tab', { name: /block rules/i }).click(); + await expect(geoSection.locator('text=0.0.0.0/0')).toBeVisible(); + + await geoSection.getByRole('tab', { name: /allow rules/i }).click(); + await expect(geoSection.locator('text=10.0.0.0/8')).toBeVisible(); + }); + + /** + * Tests that the LAN Only preset persists after save. + * Saves via UI form, then immediately reads back via API and resets Caddy + * to minimize the window where 0.0.0.0/0 blocks all traffic. + */ + test('LAN Only preset: values persist after save', async ({ page }) => { + const geoSection = page.locator('form', { has: page.getByRole('button', { name: /save geoblocking settings/i }) }); + const enableSwitch = geoSection.getByRole('switch'); + if (!(await enableSwitch.isChecked())) { + await enableSwitch.click(); + } + + await geoSection.getByRole('button', { name: /lan only/i }).click(); + await geoSection.getByRole('button', { name: /save geoblocking settings/i }).click(); + await expect(geoSection.locator('text=/saved|success/i')).toBeVisible({ timeout: 10000 }); + + // Read saved values via API, then immediately reset to stop blocking traffic + const res = await page.request.get(API_GEOBLOCK); + await resetGeoblock(page); + + const saved = await res.json(); + expect(saved.block_cidrs).toContain('0.0.0.0/0'); + expect(saved.allow_cidrs).toContain('10.0.0.0/8'); + expect(saved.allow_cidrs).toContain('172.16.0.0/12'); + expect(saved.allow_cidrs).toContain('192.168.0.0/16'); }); });