Files
Charon/tests/security/crowdsec-config.spec.ts
GitHub Actions 9cd2f5602c ix: repair CI workflow dependencies and strictness
Detailed explanation of:
- **Dependency Fix**: Added explicit Chromium installation to Firefox and WebKit security jobs. The authentication fixture depends on Chromium being present, even when testing other browsers, causing previous runs to fail setup.
- **Workflow Isolation**: Explicitly routed `tests/security/` to the dedicated "Security Enforcement" jobs and removed them from the general shards. This prevents false negatives where security config tests fail because the middleware is intentionally disabled in standard test runs.
- **Metadata**: Added `@security` tags to all security specs (`rate-limiting`, `waf-config`, etc.) to align metadata with the new execution strategy.
- **References**: Fixes CI failures in PR
2026-02-06 04:18:26 +00:00

302 lines
11 KiB
TypeScript

/**
* CrowdSec Configuration E2E Tests
*
* Tests the CrowdSec configuration page functionality including:
* - Page loading and status display
* - Preset management (view, apply, preview)
* - Configuration file management
* - Import/Export functionality
* - Console enrollment (if enabled)
*
* @see /projects/Charon/docs/plans/current_spec.md - Phase 3
*/
import { test, expect, loginUser } from '../fixtures/auth-fixtures';
import { waitForLoadingComplete, waitForToast } from '../utils/wait-helpers';
test.describe('CrowdSec Configuration @security', () => {
test.beforeEach(async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await waitForLoadingComplete(page);
await page.goto('/security/crowdsec');
await waitForLoadingComplete(page);
});
test.describe('Page Loading', () => {
test('should display CrowdSec configuration page', async ({ page }) => {
// The page should load without errors
await expect(page.getByRole('heading', { name: /crowdsec/i }).first()).toBeVisible();
});
test('should show navigation back to security dashboard', async ({ page }) => {
// Should have breadcrumb, back link, or navigation element
const backLink = page.getByRole('link', { name: /security|back/i });
const backLinkVisible = await backLink.isVisible().catch(() => false);
if (backLinkVisible) {
await expect(backLink).toBeVisible();
} else {
// Check for navigation button instead
const backButton = page.getByRole('button', { name: /back/i });
const buttonVisible = await backButton.isVisible().catch(() => false);
if (!buttonVisible) {
// Check for breadcrumbs or navigation in header
const breadcrumb = page.locator('nav, [class*="breadcrumb"], [aria-label*="breadcrumb"]');
const breadcrumbVisible = await breadcrumb.isVisible().catch(() => false);
// At minimum, the page should be loaded
if (!breadcrumbVisible) {
await expect(page).toHaveURL(/crowdsec/);
}
}
}
});
test('should display presets section', async ({ page }) => {
// Look for presets, packages, scenarios, or collections section
// This feature may not be fully implemented
const presetsSection = page.getByText(/packages|presets|scenarios|collections|bouncers/i).first();
const presetsVisible = await presetsSection.isVisible().catch(() => false);
if (presetsVisible) {
await expect(presetsSection).toBeVisible();
} else {
test.info().annotations.push({
type: 'info',
description: 'Presets section not visible - feature may not be implemented'
});
}
});
});
test.describe('Preset Management', () => {
// Preset management may not be fully implemented
test('should display list of available presets', async ({ page }) => {
await test.step('Verify presets are listed', async () => {
// Wait for presets to load
await page.waitForResponse(resp =>
resp.url().includes('/presets') || resp.url().includes('/crowdsec') || resp.url().includes('/hub'),
{ timeout: 10000 }
).catch(() => {
// If no API call, presets might be loaded statically or not implemented
});
// Should show preset cards or list items
const presetElements = page.locator('[class*="card"], [class*="preset"], button').filter({
hasText: /apply|install|owasp|basic|advanced|paranoid/i
});
const count = await presetElements.count();
if (count === 0) {
// Presets might not be implemented - check for config file management instead
const configSection = page.getByText(/configuration|file|config/i).first();
const configVisible = await configSection.isVisible().catch(() => false);
if (!configVisible) {
test.info().annotations.push({
type: 'info',
description: 'No presets displayed - feature may not be implemented'
});
}
}
});
});
test('should allow searching presets', async ({ page }) => {
const searchInput = page.getByPlaceholder(/search/i);
const searchVisible = await searchInput.isVisible().catch(() => false);
if (searchVisible) {
await test.step('Search for a preset', async () => {
await searchInput.fill('basic');
// Results should be filtered
await page.waitForTimeout(500); // Debounce
});
}
});
test('should show preset preview when selected', async ({ page }) => {
// Find and click on a preset to preview
const presetButton = page.locator('button').filter({ hasText: /preview|view|select/i }).first();
const buttonVisible = await presetButton.isVisible().catch(() => false);
if (buttonVisible) {
await presetButton.click();
// Should show preview content
await page.waitForTimeout(500);
}
});
test('should apply preset with confirmation', async ({ page }) => {
// Find apply button
const applyButton = page.locator('button').filter({ hasText: /apply/i }).first();
const buttonVisible = await applyButton.isVisible().catch(() => false);
if (buttonVisible) {
await test.step('Click apply button', async () => {
await applyButton.click();
});
await test.step('Handle confirmation or result', async () => {
// Either a confirmation dialog or success toast should appear
const confirmDialog = page.getByRole('dialog');
const dialogVisible = await confirmDialog.isVisible().catch(() => false);
if (dialogVisible) {
// Cancel to not make permanent changes
const cancelButton = page.getByRole('button', { name: /cancel/i });
await cancelButton.click();
}
});
}
});
});
test.describe('Configuration Files', () => {
test('should display configuration file list', async ({ page }) => {
// Look for file selector or list
const fileSelector = page.locator('select, [role="listbox"]').filter({
hasNotText: /sort|filter/i
}).first();
const filesVisible = await fileSelector.isVisible().catch(() => false);
if (filesVisible) {
await expect(fileSelector).toBeVisible();
}
});
test('should show file content when selected', async ({ page }) => {
// Find file selector
const fileSelector = page.locator('select').first();
const selectorVisible = await fileSelector.isVisible().catch(() => false);
if (selectorVisible) {
// Select a file - content area should appear
const contentArea = page.locator('textarea, pre, [class*="editor"]');
const contentVisible = await contentArea.isVisible().catch(() => false);
// Content area may or may not be visible depending on selection
expect(contentVisible !== undefined).toBeTruthy();
}
});
});
test.describe('Import/Export', () => {
test('should have export functionality', async ({ page }) => {
const exportButton = page.getByRole('button', { name: /export/i });
const exportVisible = await exportButton.isVisible().catch(() => false);
if (exportVisible) {
await expect(exportButton).toBeEnabled();
}
});
test('should have import functionality', async ({ page }) => {
// Look for import button or file input
const importButton = page.getByRole('button', { name: /import/i });
const importInput = page.locator('input[type="file"]');
const importVisible = await importButton.isVisible().catch(() => false);
const inputVisible = await importInput.isVisible().catch(() => false);
// Import functionality may not be implemented
if (importVisible || inputVisible) {
expect(importVisible || inputVisible).toBeTruthy();
} else {
test.info().annotations.push({
type: 'info',
description: 'Import functionality not visible - feature may not be implemented'
});
}
});
});
test.describe('Console Enrollment', () => {
test('should display console enrollment section if feature enabled', async ({ page }) => {
// Console enrollment is a feature-flagged component
const enrollmentSection = page.getByTestId('console-section').or(
page.locator('[class*="card"]').filter({ hasText: /console.*enrollment|enroll/i })
);
const enrollmentVisible = await enrollmentSection.isVisible().catch(() => false);
if (enrollmentVisible) {
await test.step('Verify enrollment form elements', async () => {
// Should have token input
const tokenInput = page.getByTestId('crowdsec-token-input').or(
page.getByPlaceholder(/token|key/i)
);
await expect(tokenInput).toBeVisible();
});
} else {
// Feature might be disabled - that's OK
test.info().annotations.push({
type: 'info',
description: 'Console enrollment feature not enabled'
});
}
});
test('should show enrollment status when enrolled', async ({ page }) => {
const statusText = page.getByTestId('console-token-state').or(
page.locator('text=/enrolled|pending|not enrolled/i')
);
const statusVisible = await statusText.isVisible().catch(() => false);
if (statusVisible) {
// Verify status is displayed
await expect(statusText).toBeVisible();
}
});
});
test.describe('Status Indicators', () => {
test('should display CrowdSec running status', async ({ page }) => {
// Look for status indicators
const statusBadge = page.locator('[class*="badge"]').filter({
hasText: /running|stopped|enabled|disabled/i
});
const statusVisible = await statusBadge.first().isVisible().catch(() => false);
if (statusVisible) {
await expect(statusBadge.first()).toBeVisible();
}
});
test('should display LAPI status', async ({ page }) => {
// LAPI ready status might be displayed
const lapiStatus = page.getByText(/lapi.*ready|lapi.*status/i);
const lapiVisible = await lapiStatus.isVisible().catch(() => false);
// LAPI status might not always be visible
expect(lapiVisible !== undefined).toBeTruthy();
});
});
test.describe('Accessibility', () => {
test('should have accessible form controls', async ({ page }) => {
// Check that inputs have associated labels
const inputs = page.locator('input:not([type="hidden"])');
const count = await inputs.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const input = inputs.nth(i);
const visible = await input.isVisible();
if (visible) {
// Input should have some form of label (explicit, aria-label, or placeholder)
const hasLabel = await input.getAttribute('aria-label') ||
await input.getAttribute('placeholder') ||
await input.getAttribute('id');
expect(hasLabel).toBeTruthy();
}
}
});
});
});