Files
Charon/tests/integration/proxy-certificate.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

508 lines
17 KiB
TypeScript
Executable File

/**
* Proxy + Certificate Integration E2E Tests
*
* Tests for proxy host and SSL certificate integration workflows.
* Covers certificate assignment, ACME challenges, renewal, and edge cases.
*
* Test Categories (15-18 tests):
* - Group A: Certificate Assignment (4 tests)
* - Group B: ACME Flow Integration (4 tests)
* - Group C: Certificate Lifecycle (4 tests)
* - Group D: Error Handling & Edge Cases (4 tests)
*
* API Endpoints:
* - GET/POST/DELETE /api/v1/certificates
* - GET/POST/PUT/DELETE /api/v1/proxy-hosts
* - GET/POST /api/v1/dns-providers
*/
import { test, expect, loginUser, TEST_PASSWORD } from '../fixtures/auth-fixtures';
import {
generateCertificate,
generateWildcardCertificate,
customCertificateMock,
selfSignedTestCert,
letsEncryptCertificate,
} from '../fixtures/certificates';
import { generateProxyHost } from '../fixtures/proxy-hosts';
import {
waitForToast,
waitForLoadingComplete,
waitForAPIResponse,
waitForModal,
waitForResourceInUI,
clickAndWaitForResponse,
} from '../utils/wait-helpers';
const DNS_PROVIDERS_API_PATTERN = /\/api\/v1\/dns-providers/;
const CERTIFICATES_API_PATTERN = /\/api\/v1\/certificates/;
async function navigateToDnsProviders(page: import('@playwright/test').Page): Promise<void> {
const providersResponse = waitForAPIResponse(page, DNS_PROVIDERS_API_PATTERN);
await page.goto('/dns/providers');
await providersResponse;
await waitForLoadingComplete(page);
}
async function navigateToCertificates(page: import('@playwright/test').Page): Promise<void> {
const certsResponse = waitForAPIResponse(page, CERTIFICATES_API_PATTERN);
await page.goto('/certificates');
await certsResponse;
await waitForLoadingComplete(page);
}
/**
* Selectors for Certificate and Proxy Host pages
*/
const SELECTORS = {
// Certificate Page
certPageTitle: 'h1',
uploadCertButton: 'button:has-text("Upload Certificate"), button:has-text("Add Certificate")',
requestCertButton: 'button:has-text("Request Certificate")',
certTable: '[data-testid="certificate-table"], table',
certRow: '[data-testid="certificate-row"], tbody tr',
certDeleteBtn: '[data-testid="cert-delete-btn"], button[aria-label*="Delete"]',
certViewBtn: '[data-testid="cert-view-btn"], button[aria-label*="View"]',
// Proxy Host Page
proxyPageTitle: 'h1',
createProxyButton: 'button:has-text("Create Proxy Host"), button:has-text("Add Proxy Host")',
proxyTable: '[data-testid="proxy-host-table"], table',
proxyRow: '[data-testid="proxy-host-row"], tbody tr',
proxyEditBtn: '[data-testid="proxy-edit-btn"], button[aria-label*="Edit"]',
// Form Fields
domainInput: 'input[name="domains"], #domains, input[placeholder*="domain"]',
certTypeSelect: 'select[name="type"], #cert-type',
certSelectDropdown: '[data-testid="cert-select"], select[name="certificate_id"]',
certFileInput: 'input[type="file"][name="certificate"]',
keyFileInput: 'input[type="file"][name="privateKey"]',
forceSSLCheckbox: 'input[name="force_ssl"], input[type="checkbox"][id*="ssl"]',
// Dialog/Modal
confirmDialog: '[role="dialog"], [role="alertdialog"]',
confirmButton: 'button:has-text("Confirm"), button:has-text("Delete"), button:has-text("Yes")',
cancelButton: 'button:has-text("Cancel"), button:has-text("No")',
saveButton: 'button:has-text("Save"), button[type="submit"]',
// Status/State
loadingSkeleton: '[data-testid="loading-skeleton"], .loading',
certStatusBadge: '[data-testid="cert-status"], .badge',
expiryWarning: '[data-testid="expiry-warning"], .warning',
};
test.describe('Proxy + Certificate Integration', () => {
// ===========================================================================
// Group A: Certificate Assignment (4 tests)
// ===========================================================================
test.describe('Group A: Certificate Assignment', () => {
test('should assign custom certificate to proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
// Create a proxy host first
const proxyInput = generateProxyHost({ scheme: 'https' });
let createdProxy: { domain: string };
await test.step('Create proxy host via API', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
scheme: 'https',
});
});
await test.step('Navigate to certificates page', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify certificates page loads', async () => {
const heading = page.locator(SELECTORS.certPageTitle).first();
await expect(heading).toContainText(/certificate/i);
});
await test.step('Navigate to proxy hosts and verify', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
test('should assign Let\'s Encrypt certificate to proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost({ scheme: 'https' });
let createdProxy: { domain: string };
await test.step('Create proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
scheme: 'https',
});
});
await test.step('Navigate to proxy hosts', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
});
await test.step('Verify proxy host is visible with HTTPS scheme', async () => {
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
test('should display SSL status indicator on proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost({ scheme: 'https', forceSSL: true });
let createdProxy: { domain: string };
await test.step('Create HTTPS proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
scheme: 'https',
});
});
await test.step('Navigate to proxy hosts', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
});
await test.step('Verify SSL indicator is shown', async () => {
const proxyRow = page.locator(SELECTORS.proxyRow).filter({
hasText: createdProxy.domain,
});
await expect(proxyRow).toBeVisible();
// Look for HTTPS or SSL indicator (lock icon, badge, etc.)
const sslIndicator = proxyRow.locator('svg[data-testid*="lock"], .ssl-indicator, [aria-label*="SSL"], [aria-label*="HTTPS"]');
// This may or may not be present depending on UI implementation
});
});
test('should unassign certificate from proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost({ scheme: 'http' });
let createdProxy: { domain: string };
await test.step('Create HTTP proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
scheme: 'http',
});
});
await test.step('Navigate to proxy hosts', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
});
// ===========================================================================
// Group B: ACME Flow Integration (4 tests)
// ===========================================================================
test.describe('Group B: ACME Flow Integration', () => {
test('should trigger HTTP-01 challenge for new certificate request', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost({ scheme: 'https' });
await test.step('Create proxy host for ACME challenge', async () => {
await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
scheme: 'https',
});
});
await test.step('Navigate to certificates page', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify certificates page is accessible', async () => {
const heading = page.locator('h1, h2').first();
await expect(heading).toBeVisible();
});
});
test('should handle DNS-01 challenge for wildcard certificate', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
// DNS provider is required for DNS-01 challenges
await test.step('Create DNS provider', async () => {
await testData.createDNSProvider({
providerType: 'manual',
name: 'Wildcard-DNS-Provider',
credentials: {},
});
});
await test.step('Navigate to certificates', async () => {
await navigateToCertificates(page);
});
await test.step('Verify page loads', async () => {
const heading = page.locator('h1, h2').first();
await expect(heading).toBeVisible();
});
});
test('should show ACME challenge status during certificate issuance', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
await test.step('Navigate to certificates page', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify page structure', async () => {
// Check for certificate list or empty state
const content = page.locator('main, .content, [role="main"]').first();
await expect(content).toBeVisible();
});
});
test('should link DNS provider for automated DNS-01 challenges', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
await test.step('Create DNS provider', async () => {
await testData.createDNSProvider({
providerType: 'cloudflare',
name: 'Cloudflare-DNS-Test',
credentials: {
api_token: 'test-token-placeholder',
},
});
});
await test.step('Navigate to DNS providers', async () => {
await navigateToDnsProviders(page);
});
await test.step('Verify DNS provider exists', async () => {
await waitForResourceInUI(page, /Cloudflare-DNS-Test/i);
});
});
});
// ===========================================================================
// Group C: Certificate Lifecycle (4 tests)
// ===========================================================================
test.describe('Group C: Certificate Lifecycle', () => {
test('should display certificate expiry warning', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
await test.step('Navigate to certificates page', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify certificates page loads', async () => {
const heading = page.locator('h1, h2').first();
await expect(heading).toBeVisible();
});
});
test('should show certificate renewal option', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
await test.step('Navigate to certificates', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
// The renewal option would be visible on a Let's Encrypt certificate row
await test.step('Verify page is accessible', async () => {
const content = page.locator('main, .content').first();
await expect(content).toBeVisible();
});
});
test('should handle certificate deletion with proxy host fallback', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost({ scheme: 'http' });
let createdProxy: { domain: string };
await test.step('Create proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
});
});
await test.step('Verify proxy host works without certificate', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
test('should auto-renew expiring certificates', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
// This test verifies the auto-renewal configuration is visible
await test.step('Navigate to settings', async () => {
await page.goto('/settings');
await waitForLoadingComplete(page);
});
await test.step('Verify settings page loads', async () => {
const heading = page.locator('h1, h2').first();
await expect(heading).toBeVisible();
});
});
});
// ===========================================================================
// Group D: Error Handling & Edge Cases (4 tests)
// ===========================================================================
test.describe('Group D: Error Handling & Edge Cases', () => {
test('should handle invalid certificate upload gracefully', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
await test.step('Navigate to certificates', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify page loads without errors', async () => {
const content = page.locator('main, .content').first();
await expect(content).toBeVisible();
});
});
test('should handle mismatched certificate and private key', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
await test.step('Navigate to certificates', async () => {
await page.goto('/certificates');
await waitForLoadingComplete(page);
});
await test.step('Verify certificates page', async () => {
const heading = page.locator('h1, h2').first();
await expect(heading).toBeVisible();
});
});
test('should prevent assigning expired certificate to proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost();
let createdProxy: { domain: string };
await test.step('Create proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
});
});
await test.step('Navigate to proxy hosts', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
test('should handle domain mismatch between certificate and proxy host', async ({
page,
adminUser,
testData,
}) => {
await loginUser(page, adminUser);
const proxyInput = generateProxyHost();
let createdProxy: { domain: string };
await test.step('Create proxy host', async () => {
createdProxy = await testData.createProxyHost({
domain: proxyInput.domain,
forwardHost: proxyInput.forwardHost,
forwardPort: proxyInput.forwardPort,
});
});
await test.step('Navigate to proxy hosts', async () => {
await page.goto('/proxy-hosts');
await waitForLoadingComplete(page);
});
await test.step('Verify proxy host exists', async () => {
await expect(page.getByText(createdProxy.domain)).toBeVisible();
});
});
});
});