Files
caddy-proxy-manager/tests/e2e/functional/path-prefix-rewrite.spec.ts
fuomag9 4b5323a7bf feat: add structured redirects and path prefix rewrite for proxy hosts
Adds two new UI-configurable Caddy patterns that previously required raw JSON:
- Per-path redirect rules (from/to/status) emitted as a subroute handler before
  auth so .well-known paths work without login; supports full URLs, cross-domain
  targets, and wildcard path patterns (e.g. /.well-known/*)
- Path prefix rewrite that prepends a segment to every request before proxying
  (e.g. /recipes → upstream sees /recipes/original/path)

Config is stored in the existing meta JSON column (no schema migration). Includes
integration tests for meta serialization and E2E functional tests against a real
Caddy instance covering relative/absolute destinations, all 3xx status codes, and
various wildcard combinations. Adds traefik/whoami to the test stack to verify
rewritten paths actually reach the upstream.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 17:53:33 +01:00

64 lines
2.5 KiB
TypeScript

/**
* Functional tests: path prefix rewrite.
*
* Creates a proxy host with a path prefix rewrite (/api) pointing at the
* whoami-server, which reflects the full request line in its response body.
* This lets us assert that Caddy rewrote the path before forwarding, e.g.
* a client request for /users arrives at the upstream as /api/users.
*
* Domain: func-rewrite.test
*/
import { test, expect } from '@playwright/test';
import { httpGet, injectFormFields, waitForRoute } from '../../helpers/http';
const DOMAIN = 'func-rewrite.test';
test.describe.serial('Path Prefix Rewrite', () => {
test('setup: create proxy host with path prefix rewrite', async ({ page }) => {
await page.goto('/proxy-hosts');
await page.getByRole('button', { name: /create host/i }).click();
await expect(page.getByRole('dialog')).toBeVisible();
await page.getByLabel('Name').fill('Functional Path Prefix Rewrite Test');
await page.getByLabel(/domains/i).fill(DOMAIN);
// whoami-server listens on port 80 by default
await page.getByPlaceholder('10.0.0.5:8080').first().fill('whoami-server:80');
// Fill in the path prefix rewrite field
await page.getByLabel('Path Prefix Rewrite').fill('/api');
await injectFormFields(page, { ssl_forced_present: 'on' });
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Functional Path Prefix Rewrite Test')).toBeVisible({ timeout: 10_000 });
await waitForRoute(DOMAIN);
});
test('request path is prepended with the prefix before reaching the upstream', async () => {
const res = await httpGet(DOMAIN, '/users');
expect(res.status).toBe(200);
// traefik/whoami echoes the request line, e.g. "GET /api/users HTTP/1.1"
expect(res.body).toContain('/api/users');
});
test('root path is prepended with the prefix', async () => {
const res = await httpGet(DOMAIN, '/');
expect(res.status).toBe(200);
expect(res.body).toContain('/api/');
});
test('nested path is prepended with the prefix', async () => {
const res = await httpGet(DOMAIN, '/items/42/details');
expect(res.status).toBe(200);
expect(res.body).toContain('/api/items/42/details');
});
test('original path without prefix is NOT sent to the upstream', async () => {
const res = await httpGet(DOMAIN, '/users');
expect(res.status).toBe(200);
// The upstream must NOT see the bare /users path — it should see /api/users
expect(res.body).not.toMatch(/^GET \/users /m);
});
});