/** * Proxy + Certificate Integration E2E Tests (Phase 6.2) * * 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, clickAndWaitForResponse, } from '../utils/wait-helpers'; /** * 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 page.goto('/certificates'); await waitForLoadingComplete(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 page.goto('/dns-providers'); await waitForLoadingComplete(page); }); await test.step('Verify DNS provider exists', async () => { // The provider name contains namespace prefix const content = page.locator('main, table, .content').first(); await expect(content).toBeVisible(); }); }); }); // =========================================================================== // 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(); }); }); }); });