339 lines
14 KiB
TypeScript
339 lines
14 KiB
TypeScript
import { test, expect } from './fixtures/auth';
|
|
import { TIMEOUTS } from './utils/test-data';
|
|
import { TestHelpers } from './utils/test-helpers';
|
|
|
|
test.describe('WebSocket Sync Progress', () => {
|
|
let helpers: TestHelpers;
|
|
|
|
test.beforeEach(async ({ adminPage }) => {
|
|
helpers = new TestHelpers(adminPage);
|
|
await helpers.navigateToPage('/sources');
|
|
});
|
|
|
|
test('should establish WebSocket connection for sync progress', async ({ adminPage: page }) => {
|
|
// Create a test source first
|
|
await page.click('button:has-text("Add Source"), [data-testid="add-source"]');
|
|
await page.fill('input[name="name"]', 'WebSocket Test Source');
|
|
await page.selectOption('select[name="type"]', 'webdav');
|
|
await page.fill('input[name="server_url"]', 'https://test.webdav.server');
|
|
await page.fill('input[name="username"]', 'testuser');
|
|
await page.fill('input[name="password"]', 'testpass');
|
|
await page.click('button[type="submit"]');
|
|
|
|
// Wait for source to be created
|
|
await helpers.waitForToast();
|
|
|
|
// Find the created source and trigger sync
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("WebSocket Test Source")').first();
|
|
await expect(sourceRow).toBeVisible();
|
|
|
|
// Click sync button
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait for sync progress display to appear
|
|
await expect(page.locator('[data-testid="sync-progress"], .sync-progress')).toBeVisible({ timeout: TIMEOUTS.medium });
|
|
|
|
// Check that WebSocket connection is established
|
|
// We'll monitor network traffic or check for specific UI indicators
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
|
|
// Should show connection status
|
|
await expect(progressDisplay.locator(':has-text("Connected"), :has-text("Connecting")')).toBeVisible();
|
|
|
|
// Should receive progress updates
|
|
await expect(progressDisplay.locator('[data-testid="progress-phase"], .progress-phase')).toBeVisible();
|
|
|
|
// Should show progress data
|
|
await expect(progressDisplay.locator('[data-testid="files-processed"], .files-processed')).toBeVisible();
|
|
});
|
|
|
|
test('should handle WebSocket connection errors gracefully', async ({ adminPage: page }) => {
|
|
// Mock WebSocket connection failure
|
|
await page.route('**/sync/progress/ws**', route => {
|
|
route.abort('connectionrefused');
|
|
});
|
|
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Error Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Error Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Should show connection error
|
|
await expect(page.locator('[data-testid="connection-error"], .connection-error, :has-text("Connection failed")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
|
});
|
|
|
|
test('should automatically reconnect on WebSocket disconnection', async ({ adminPage: page }) => {
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Reconnect Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Reconnect Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait for initial connection
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay.locator(':has-text("Connected")')).toBeVisible();
|
|
|
|
// Simulate connection interruption by intercepting WebSocket and closing it
|
|
await page.evaluate(() => {
|
|
// Find any active WebSocket connections and close them
|
|
// This is a simplified simulation - in real tests you might use more sophisticated mocking
|
|
if ((window as any).testWebSocket) {
|
|
(window as any).testWebSocket.close();
|
|
}
|
|
});
|
|
|
|
// Should show reconnecting status
|
|
await expect(progressDisplay.locator(':has-text("Reconnecting"), :has-text("Disconnected")')).toBeVisible({ timeout: TIMEOUTS.short });
|
|
|
|
// Should eventually reconnect
|
|
await expect(progressDisplay.locator(':has-text("Connected")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
|
});
|
|
|
|
test('should display real-time progress updates via WebSocket', async ({ adminPage: page }) => {
|
|
// Create a source and start sync
|
|
await helpers.createTestSource('Progress Updates Test', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Progress Updates Test")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay).toBeVisible();
|
|
|
|
// Should show different phases over time
|
|
const phases = ['initializing', 'discovering', 'processing'];
|
|
|
|
for (const phase of phases) {
|
|
// Wait for phase to appear (with timeout since sync might be fast)
|
|
try {
|
|
await expect(progressDisplay.locator(`:has-text("${phase}")`)).toBeVisible({ timeout: TIMEOUTS.short });
|
|
} catch (e) {
|
|
// Phase might have passed quickly, continue to next
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Should show numerical progress
|
|
await expect(progressDisplay.locator('[data-testid="files-processed"], .files-processed')).toBeVisible();
|
|
await expect(progressDisplay.locator('[data-testid="progress-percentage"], .progress-percentage')).toBeVisible();
|
|
});
|
|
|
|
test('should handle multiple concurrent WebSocket connections', async ({ adminPage: page }) => {
|
|
// Create multiple sources
|
|
const sourceNames = ['Multi Source 1', 'Multi Source 2', 'Multi Source 3'];
|
|
|
|
for (const name of sourceNames) {
|
|
await helpers.createTestSource(name, 'webdav');
|
|
}
|
|
|
|
// Start sync on all sources
|
|
for (const name of sourceNames) {
|
|
const sourceRow = page.locator(`[data-testid="source-item"]:has-text("${name}")`);
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait a moment between syncs
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// Should have multiple progress displays
|
|
const progressDisplays = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplays).toHaveCount(3, { timeout: TIMEOUTS.medium });
|
|
|
|
// Each should show connection status
|
|
for (let i = 0; i < 3; i++) {
|
|
const display = progressDisplays.nth(i);
|
|
await expect(display.locator(':has-text("Connected"), :has-text("Connecting")')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should authenticate WebSocket connection with JWT token', async ({ adminPage: page }) => {
|
|
// Intercept WebSocket requests to verify token is sent
|
|
let websocketToken = '';
|
|
|
|
await page.route('**/sync/progress/ws**', route => {
|
|
websocketToken = new URL(route.request().url()).searchParams.get('token') || '';
|
|
route.continue();
|
|
});
|
|
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Auth Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Auth Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait for WebSocket connection attempt
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Verify token was sent
|
|
expect(websocketToken).toBeTruthy();
|
|
expect(websocketToken.length).toBeGreaterThan(20); // JWT tokens are typically longer
|
|
});
|
|
|
|
test('should handle WebSocket authentication failures', async ({ adminPage: page }) => {
|
|
// Mock authentication failure
|
|
await page.route('**/sync/progress/ws**', route => {
|
|
if (route.request().url().includes('token=')) {
|
|
route.fulfill({ status: 401, body: 'Unauthorized' });
|
|
} else {
|
|
route.continue();
|
|
}
|
|
});
|
|
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Auth Fail Test', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Auth Fail Test")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Should show authentication error
|
|
await expect(page.locator(':has-text("Authentication failed"), :has-text("Unauthorized")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
|
});
|
|
|
|
test('should properly clean up WebSocket connections on component unmount', async ({ adminPage: page }) => {
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Cleanup Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Cleanup Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait for progress display
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay).toBeVisible();
|
|
|
|
// Navigate away from the page
|
|
await page.goto('/documents');
|
|
|
|
// Navigate back
|
|
await page.goto('/sources');
|
|
|
|
// The progress display should be properly cleaned up and recreated if sync is still active
|
|
// This tests that WebSocket connections are properly closed on unmount
|
|
|
|
// If sync is still running, progress should reappear
|
|
const sourceRowAfter = page.locator('[data-testid="source-item"]:has-text("Cleanup Test Source")').first();
|
|
if (await sourceRowAfter.locator(':has-text("Syncing")').isVisible()) {
|
|
await expect(page.locator('[data-testid="sync-progress"], .sync-progress')).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should handle WebSocket message parsing errors', async ({ adminPage: page }) => {
|
|
// Mock WebSocket with malformed messages
|
|
await page.addInitScript(() => {
|
|
const originalWebSocket = window.WebSocket;
|
|
window.WebSocket = class extends originalWebSocket {
|
|
constructor(url: string, protocols?: string | string[]) {
|
|
super(url, protocols);
|
|
|
|
// Override message handling to send malformed data
|
|
setTimeout(() => {
|
|
if (this.onmessage) {
|
|
this.onmessage({
|
|
data: 'invalid json {malformed',
|
|
type: 'message'
|
|
} as MessageEvent);
|
|
}
|
|
}, 1000);
|
|
}
|
|
};
|
|
});
|
|
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Parse Error Test', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Parse Error Test")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Should handle parsing errors gracefully (not crash the UI)
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay).toBeVisible();
|
|
|
|
// Check console for error messages (optional)
|
|
const logs = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
logs.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.waitForTimeout(3000);
|
|
|
|
// Verify the UI didn't crash (still showing some content)
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
|
|
test('should display WebSocket connection status indicators', async ({ adminPage: page }) => {
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Status Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Status Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay).toBeVisible();
|
|
|
|
// Should show connecting status initially
|
|
await expect(progressDisplay.locator('[data-testid="connection-status"], .connection-status')).toContainText(/connecting|connected/i);
|
|
|
|
// Should show connected status once established
|
|
await expect(progressDisplay.locator(':has-text("Connected")')).toBeVisible({ timeout: TIMEOUTS.medium });
|
|
|
|
// Should have visual indicators (icons, colors, etc.)
|
|
await expect(progressDisplay.locator('.connection-indicator, [data-testid="connection-indicator"]')).toBeVisible();
|
|
});
|
|
|
|
test('should support WebSocket ping/pong for connection health', async ({ adminPage: page }) => {
|
|
// This test verifies that the WebSocket connection uses ping/pong for health checks
|
|
|
|
let pingReceived = false;
|
|
|
|
// Mock WebSocket to track ping messages
|
|
await page.addInitScript(() => {
|
|
const originalWebSocket = window.WebSocket;
|
|
window.WebSocket = class extends originalWebSocket {
|
|
send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
|
|
if (data === 'ping') {
|
|
(window as any).pingReceived = true;
|
|
}
|
|
super.send(data);
|
|
}
|
|
};
|
|
});
|
|
|
|
// Create and sync a source
|
|
await helpers.createTestSource('Ping Test Source', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Ping Test Source")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Wait for connection and potential ping messages
|
|
await page.waitForTimeout(5000);
|
|
|
|
// Check if ping was sent (this is implementation-dependent)
|
|
const pingWasSent = await page.evaluate(() => (window as any).pingReceived);
|
|
|
|
// Note: This test might need adjustment based on actual ping/pong implementation
|
|
// The important thing is that the connection remains healthy
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay.locator(':has-text("Connected")')).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('WebSocket Sync Progress - Cross-browser Compatibility', () => {
|
|
test('should work in different browser engines', async ({ adminPage: page }) => {
|
|
// This test would run across different browsers (Chrome, Firefox, Safari)
|
|
// The test framework should handle this automatically
|
|
|
|
// Create and sync a source
|
|
const helpers = new TestHelpers(page);
|
|
await helpers.navigateToPage('/sources');
|
|
await helpers.createTestSource('Cross Browser Test', 'webdav');
|
|
|
|
const sourceRow = page.locator('[data-testid="source-item"]:has-text("Cross Browser Test")').first();
|
|
await sourceRow.locator('button:has-text("Sync")').click();
|
|
|
|
// Should work regardless of browser
|
|
const progressDisplay = page.locator('[data-testid="sync-progress"], .sync-progress');
|
|
await expect(progressDisplay).toBeVisible();
|
|
await expect(progressDisplay.locator(':has-text("Connected"), :has-text("Connecting")')).toBeVisible();
|
|
});
|
|
}); |