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
302 lines
11 KiB
TypeScript
Executable File
302 lines
11 KiB
TypeScript
Executable File
/**
|
|
* 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
|
|
*/
|
|
|
|
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) {
|
|
await expect(importButton.or(importInput)).toBeVisible();
|
|
} 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();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|