Files
Charon/tests/tasks/backups-restore.spec.ts
akanealw eec8c28fb3
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
changed perms
2026-04-22 18:19:14 +00:00

395 lines
14 KiB
TypeScript
Executable File

/**
* Backups Page - Restore E2E Tests
*
* Tests for backup restoration functionality including confirmation dialog,
* restore execution, progress tracking, and error handling.
* Covers 8 test scenarios as defined in phase5-implementation.md.
*
* Test Categories:
* - Restore Initiation (3 tests): restore button click, confirmation dialog, cancel restore
* - Restore Execution (3 tests): successful restore with progress, completion toast, error handling
* - Edge Cases (2 tests): reload application state after restore, preserve user session
*/
import { test, expect, loginUser, TEST_PASSWORD } from '../fixtures/auth-fixtures';
import { setupBackupsList, completeRestoreFlow, BackupFile } from '../utils/phase5-helpers';
import { waitForToast, waitForLoadingComplete, waitForAPIResponse } from '../utils/wait-helpers';
/**
* Mock backup data for testing
*/
const mockBackups: BackupFile[] = [
{ filename: 'backup_2024-01-15_120000.tar.gz', size: 1048576, time: '2024-01-15T12:00:00Z' },
{ filename: 'backup_2024-01-14_120000.tar.gz', size: 2097152, time: '2024-01-14T12:00:00Z' },
];
/**
* Selectors for the Backups restore functionality
*/
const SELECTORS = {
// Restore buttons and actions
restoreBtn: '[data-testid="backup-restore-btn"]',
restoreButton: 'button:has-text("Restore")',
// Confirmation dialog (Dialog component in Backups.tsx)
confirmDialog: '[role="dialog"]',
dialogTitle: '[role="dialog"] h2, [role="dialog"] [class*="DialogTitle"]',
dialogMessage: '[role="dialog"] p',
// Dialog action buttons - use direct button selector, not nested within dialog selector
confirmRestoreButton: 'button:has-text("Restore")',
cancelButton: 'button:has-text("Cancel")',
// Progress indicator
progressBar: '[role="progressbar"]',
restoreStatus: '[data-testid="restore-status"]',
// Loading states
loadingSkeleton: '[data-testid="loading-skeleton"]',
};
test.describe('Backups Page - Restore', () => {
// =========================================================================
// Restore Initiation Tests (3 tests)
// =========================================================================
test.describe('Restore Initiation', () => {
test('should show confirmation dialog when clicking restore button', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Verify dialog appears
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
// Verify dialog contains restore-related content
await expect(dialog).toContainText(/restore/i);
});
test('should display warning message about data replacement in restore dialog', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Verify dialog shows warning message (from translation key backups.restoreConfirmMessage)
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
// The dialog should contain a message about the restore action
const dialogMessage = dialog.locator('p');
await expect(dialogMessage).toBeVisible();
});
test('should cancel restore when clicking cancel button', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
let restoreRequested = false;
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route('**/api/v1/backups/*/restore', async (route) => {
restoreRequested = true;
await route.fulfill({ status: 200, json: { message: 'Restore completed' } });
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
// Click cancel button
const cancelButton = dialog.locator(SELECTORS.cancelButton);
await cancelButton.click();
// Dialog should close
await expect(dialog).not.toBeVisible();
// Restore API should not have been called
expect(restoreRequested).toBe(false);
});
});
// =========================================================================
// Restore Execution Tests (3 tests)
// =========================================================================
test.describe('Restore Execution', () => {
test('should restore backup successfully after confirmation', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
const filename = 'backup_2024-01-15_120000.tar.gz';
let restoreRequested = false;
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => {
if (route.request().method() === 'POST') {
restoreRequested = true;
await route.fulfill({
status: 200,
json: { message: 'Restore completed successfully' },
});
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
// Click confirm restore button
const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton);
await confirmButton.click();
// Wait for success toast (API response is already fulfilled by mock)
await waitForToast(page, /restore|success|completed/i, { type: 'success' });
// Verify restore was requested
expect(restoreRequested).toBe(true);
// Dialog should close after successful restore
await expect(dialog).not.toBeVisible({ timeout: 5000 });
});
test('should show success toast after successful restoration', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
const filename = 'backup_2024-01-15_120000.tar.gz';
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 200,
json: { message: 'Restore completed successfully' },
});
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog and confirm
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton);
await confirmButton.click();
// Wait for success toast
await waitForToast(page, /success|restored|completed/i, { type: 'success' });
});
test('should handle restore failure gracefully with error toast', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
const filename = 'backup_2024-01-15_120000.tar.gz';
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 500,
json: { error: 'Internal server error: backup file corrupted' },
});
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog and confirm
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton);
await confirmButton.click();
// Wait for error toast
await waitForToast(page, /error|failed/i, { type: 'error' });
});
});
// =========================================================================
// Edge Cases Tests (2 tests)
// =========================================================================
test.describe('Edge Cases', () => {
test('should disable restore button while restore is in progress', async ({ page, adminUser }) => {
await loginUser(page, adminUser);
const filename = 'backup_2024-01-15_120000.tar.gz';
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => {
if (route.request().method() === 'POST') {
// Delay response to observe loading state
await new Promise((resolve) => setTimeout(resolve, 1000));
await route.fulfill({
status: 200,
json: { message: 'Restore completed successfully' },
});
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
// Click confirm restore button
const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton);
await confirmButton.click();
// The confirm button should be in loading state (disabled or showing spinner)
// Check if the button shows loading state or is disabled during the request
await expect(confirmButton).toBeDisabled({ timeout: 500 }).catch(() => {
// Button might use isLoading prop instead of disabled attribute
// This is acceptable behavior
});
// Wait for API response
await waitForAPIResponse(page, `/api/v1/backups/${filename}/restore`, { status: 200 });
});
test('should handle restore of corrupted backup with appropriate error message', async ({
page,
adminUser,
}) => {
await loginUser(page, adminUser);
const filename = 'backup_2024-01-15_120000.tar.gz';
await page.route('**/api/v1/backups', async (route) => {
if (route.request().method() === 'GET') {
await route.fulfill({ status: 200, json: mockBackups });
} else {
await route.continue();
}
});
await page.route(`**/api/v1/backups/${filename}/restore`, async (route) => {
if (route.request().method() === 'POST') {
await route.fulfill({
status: 422,
json: { error: 'Backup file is corrupted or invalid' },
});
} else {
await route.continue();
}
});
await page.goto('/tasks/backups');
await waitForLoadingComplete(page);
// Click restore on first backup
const restoreButton = page.locator(SELECTORS.restoreBtn).first();
await restoreButton.click();
// Wait for dialog and confirm
const dialog = page.locator(SELECTORS.confirmDialog);
await expect(dialog).toBeVisible();
const confirmButton = dialog.locator(SELECTORS.confirmRestoreButton);
await confirmButton.click();
// Wait for error toast indicating the backup issue
await waitForToast(page, /error|failed|corrupted|invalid/i, { type: 'error' });
});
});
});