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
508 lines
17 KiB
TypeScript
Executable File
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();
|
|
});
|
|
});
|
|
});
|
|
});
|