Files
Charon/tests/security-enforcement/zzz-security-ui/crowdsec-import.spec.ts
GitHub Actions 3169b05156 fix: skip incomplete system log viewer tests
- Marked 12 tests as skip pending feature implementation
- Features tracked in GitHub issue #686 (system log viewer feature completion)
- Tests cover sorting by timestamp/level/method/URI/status, pagination controls, filtering by text/level, download functionality
- Unblocks Phase 2 at 91.7% pass rate to proceed to Phase 3 security enforcement validation
- TODO comments in code reference GitHub #686 for feature completion tracking
- Tests skipped: Pagination (3), Search/Filter (2), Download (2), Sorting (1), Log Display (4)
2026-02-09 21:55:55 +00:00

335 lines
11 KiB
TypeScript

/**
* Import CrowdSec Configuration - E2E Tests
*
* Tests for the CrowdSec configuration import functionality.
* Covers 8 test scenarios as defined in phase5-implementation.md.
*
* Test Categories:
* - Page Layout (2 tests): heading, form display
* - File Validation (3 tests): valid file, invalid format, missing fields
* - Import Execution (3 tests): import success, error handling, already exists
*/
import { test, expect, loginUser } from '../../fixtures/auth-fixtures';
import { waitForToast, waitForLoadingComplete, waitForAPIResponse } from '../../utils/wait-helpers';
/**
* Selectors for the Import CrowdSec page
*/
const SELECTORS = {
// Page elements
pageTitle: 'h1',
fileInput: '[data-testid="crowdsec-import-file"]',
progress: '[data-testid="import-progress"]',
// Buttons
importButton: 'button:has-text("Import")',
// Error/success messages
errorMessage: '.bg-red-900',
successToast: '[data-testid="toast-success"]',
};
/**
* Mock CrowdSec configuration for testing
*/
const mockCrowdSecConfig = {
lapi_url: 'http://crowdsec:8080',
bouncer_api_key: 'test-api-key',
mode: 'live',
};
/**
* Helper to create a mock tar.gz file buffer
*/
function createMockTarGzBuffer(): Buffer {
return Buffer.from('mock tar.gz content for crowdsec config');
}
/**
* Helper to create a mock zip file buffer
*/
function createMockZipBuffer(): Buffer {
return Buffer.from('mock zip content for crowdsec config');
}
test.describe('Import CrowdSec Configuration', () => {
// =========================================================================
// Page Layout Tests (2 tests)
// =========================================================================
test.describe('Page Layout', () => {
test('should display import page with correct heading', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
await expect(page.locator(SELECTORS.pageTitle)).toContainText(/crowdsec|import/i);
});
test('should show file upload form with accepted formats', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Verify file input is visible
const fileInput = page.locator(SELECTORS.fileInput);
await expect(fileInput).toBeVisible();
// Verify it accepts proper file types (.tar.gz, .zip)
await expect(fileInput).toHaveAttribute('accept', /\.tar\.gz|\.zip/);
// Verify import button exists
const importButton = page.locator(SELECTORS.importButton);
await expect(importButton).toBeVisible();
// Verify progress section exists
await expect(page.locator(SELECTORS.progress)).toBeVisible();
});
});
// =========================================================================
// File Validation Tests (3 tests)
// =========================================================================
test.describe('File Validation', () => {
test('should accept valid .tar.gz configuration files', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
// Mock backup and import APIs
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 201,
json: { filename: 'pre-import-backup.tar.gz', size: 1000, time: new Date().toISOString() },
});
} else {
await route.continue();
}
});
await page.route('**/api/v1/admin/crowdsec/import', async (route) => {
await route.fulfill({
status: 200,
json: { message: 'Import successful' },
});
});
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Upload .tar.gz file
const fileInput = page.locator(SELECTORS.fileInput);
await fileInput.setInputFiles({
name: 'crowdsec-config.tar.gz',
mimeType: 'application/gzip',
buffer: createMockTarGzBuffer(),
});
// Verify file was accepted (import button should be enabled)
await expect(page.locator(SELECTORS.importButton)).toBeEnabled();
});
test('should accept valid .zip configuration files', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
// Mock backup and import APIs
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 201,
json: { filename: 'pre-import-backup.tar.gz', size: 1000, time: new Date().toISOString() },
});
} else {
await route.continue();
}
});
await page.route('**/api/v1/admin/crowdsec/import', async (route) => {
await route.fulfill({
status: 200,
json: { message: 'Import successful' },
});
});
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Upload .zip file
const fileInput = page.locator(SELECTORS.fileInput);
await fileInput.setInputFiles({
name: 'crowdsec-config.zip',
mimeType: 'application/zip',
buffer: createMockZipBuffer(),
});
// Verify file was accepted (import button should be enabled)
await expect(page.locator(SELECTORS.importButton)).toBeEnabled();
});
test('should disable import button when no file selected', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Import button should be disabled when no file is selected
await expect(page.locator(SELECTORS.importButton)).toBeDisabled();
});
});
// =========================================================================
// Import Execution Tests (3 tests)
// =========================================================================
test.describe('Import Execution', () => {
test('should create backup before import and complete successfully', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
let backupCalled = false;
let importCalled = false;
const callOrder: string[] = [];
// Mock backup API
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'POST') {
backupCalled = true;
callOrder.push('backup');
await route.fulfill({
status: 201,
json: { filename: 'pre-import-backup.tar.gz', size: 1000, time: new Date().toISOString() },
});
} else {
await route.continue();
}
});
// Mock import API
await page.route('**/api/v1/admin/crowdsec/import', async (route) => {
importCalled = true;
callOrder.push('import');
await route.fulfill({
status: 200,
json: { message: 'CrowdSec configuration imported successfully' },
});
});
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Upload file
const fileInput = page.locator(SELECTORS.fileInput);
await fileInput.setInputFiles({
name: 'crowdsec-config.tar.gz',
mimeType: 'application/gzip',
buffer: createMockTarGzBuffer(),
});
// Click import button and wait for import API response concurrently
await Promise.all([
page.waitForResponse(r => r.url().includes('/api/v1/admin/crowdsec/import') && r.status() === 200),
page.locator(SELECTORS.importButton).click(),
]);
// Verify backup was called FIRST, then import
expect(backupCalled).toBe(true);
expect(importCalled).toBe(true);
expect(callOrder).toEqual(['backup', 'import']);
// Verify success toast - use specific text match
await expect(page.getByText('CrowdSec config imported')).toBeVisible({ timeout: 10000 });
});
test('should handle import errors gracefully', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
// Mock backup API (success)
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 201,
json: { filename: 'pre-import-backup.tar.gz', size: 1000, time: new Date().toISOString() },
});
} else {
await route.continue();
}
});
// Mock import API (failure)
await page.route('**/api/v1/admin/crowdsec/import', async (route) => {
await route.fulfill({
status: 400,
json: { error: 'Invalid configuration format: missing required field "lapi_url"' },
});
});
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Upload file
const fileInput = page.locator(SELECTORS.fileInput);
await fileInput.setInputFiles({
name: 'crowdsec-config.tar.gz',
mimeType: 'application/gzip',
buffer: createMockTarGzBuffer(),
});
// Click import button and wait for import API response concurrently
await Promise.all([
page.waitForResponse(r => r.url().includes('/api/v1/admin/crowdsec/import') && r.status() === 400),
page.locator(SELECTORS.importButton).click(),
]);
// Verify error toast - use specific text match
await expect(page.getByText(/Import failed/i)).toBeVisible({ timeout: 10000 });
});
test('should show loading state during import', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
// Mock backup API with delay
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'POST') {
await new Promise((resolve) => setTimeout(resolve, 300));
await route.fulfill({
status: 201,
json: { filename: 'pre-import-backup.tar.gz', size: 1000, time: new Date().toISOString() },
});
} else {
await route.continue();
}
});
// Mock import API with delay
await page.route('**/api/v1/admin/crowdsec/import', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 500));
await route.fulfill({
status: 200,
json: { message: 'Import successful' },
});
});
await page.goto('/tasks/import/crowdsec');
await waitForLoadingComplete(page);
// Upload file
const fileInput = page.locator(SELECTORS.fileInput);
await fileInput.setInputFiles({
name: 'crowdsec-config.tar.gz',
mimeType: 'application/gzip',
buffer: createMockTarGzBuffer(),
});
// Set up response promise before clicking to capture loading state
const importResponsePromise = page.waitForResponse(r => r.url().includes('/api/v1/admin/crowdsec/import') && r.status() === 200);
// Click import button
const importButton = page.locator(SELECTORS.importButton);
await importButton.click();
// Button should be disabled during import (loading state)
await expect(importButton).toBeDisabled();
// Wait for import to complete
await importResponsePromise;
// Button should be enabled again after completion
await expect(importButton).toBeEnabled();
});
});
});