Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions frontend/src/lib/k8s/api/v2/webSocket.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,28 +409,39 @@

describe('WebSocket error handling', () => {
it('should handle polling timeout', async () => {
vi.useFakeTimers();

// Mock WebSocket to never open
const mockWS = vi.spyOn(window, 'WebSocket').mockImplementation(() => {
const ws = new EventTarget() as WebSocket;
Object.defineProperty(ws, 'readyState', { value: WebSocket.CONNECTING });
Object.defineProperty(ws, 'send', { value: null });
Object.defineProperty(ws, 'send', { value: vi.fn() });
Object.defineProperty(ws, 'close', { value: vi.fn() });
return ws;
});

const path = '/api/v1/pods';
const query = 'watch=true';

let error: Error | null = null;
try {
await WebSocketManager.subscribe(clusterName, path, query, onMessage);
} catch (e) {
const subscribePromise = WebSocketManager.subscribe(
clusterName,
path,
query,
onMessage
).catch(e => {
error = e as Error;
}
});

// Fast-forward time to trigger the timeout
await vi.advanceTimersByTimeAsync(5000);
await subscribePromise;

expect(error).toBeTruthy();
expect(error?.message).toBe("Cannot read properties of null (reading 'send')");
expect(error?.message).toBe('WebSocket connection timeout');

Check failure on line 441 in frontend/src/lib/k8s/api/v2/webSocket.test.ts

View workflow job for this annotation

GitHub Actions / test (20.x, ubuntu-22.04)

src/lib/k8s/api/v2/webSocket.test.ts > WebSocket Tests > WebSocket error handling > should handle polling timeout

AssertionError: expected 'Cannot read properties of null (readi…' to be 'WebSocket connection timeout' // Object.is equality Expected: "WebSocket connection timeout" Received: "Cannot read properties of null (reading 'send')" ❯ src/lib/k8s/api/v2/webSocket.test.ts:441:30

mockWS.mockRestore();
vi.useRealTimers();
});

it('should handle reconnection and resubscribe', async () => {
Expand Down
31 changes: 29 additions & 2 deletions frontend/src/lib/k8s/api/v2/webSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { makeUrl } from './makeUrl';
// Constants for WebSocket connection
export const BASE_WS_URL = BASE_HTTP_URL.replace('http', 'ws');
export const MULTIPLEXER_ENDPOINT = 'wsMultiplexer';
const CONNECTION_TIMEOUT_MS = 5000;

/**
* Multiplexer endpoint for WebSocket connections
Expand Down Expand Up @@ -132,12 +133,27 @@ export const WebSocketManager = {

// Wait for existing connection attempt if in progress
if (this.connecting) {
return new Promise(resolve => {
const checkConnection = setInterval(() => {
return new Promise((resolve, reject) => {
// eslint-disable-next-line prefer-const
let checkConnection: ReturnType<typeof setInterval>;

const timeout = setTimeout(() => {
clearInterval(checkConnection);
reject(new Error('WebSocket connection timeout'));
}, CONNECTION_TIMEOUT_MS);

checkConnection = setInterval(() => {
if (this.socketMultiplexer?.readyState === WebSocket.OPEN) {
clearInterval(checkConnection);
clearTimeout(timeout);
resolve(this.socketMultiplexer);
}
// Also check if connection failed
if (!this.connecting) {
clearInterval(checkConnection);
clearTimeout(timeout);
reject(new Error('WebSocket connection failed'));
}
}, 100);
});
}
Expand All @@ -148,7 +164,17 @@ export const WebSocketManager = {
return new Promise((resolve, reject) => {
const socket = new WebSocket(wsUrl);

// Add connection timeout
const connectionTimeout = setTimeout(() => {
this.connecting = false;
if (socket.close) {
socket.close();
}
reject(new Error('WebSocket connection timeout'));
}, CONNECTION_TIMEOUT_MS);

socket.onopen = () => {
clearTimeout(connectionTimeout);
this.socketMultiplexer = socket;
this.connecting = false;

Expand All @@ -164,6 +190,7 @@ export const WebSocketManager = {
socket.onmessage = this.handleWebSocketMessage.bind(this);

socket.onerror = event => {
clearTimeout(connectionTimeout);
this.connecting = false;
console.error('WebSocket error:', event);
reject(new Error('WebSocket connection failed'));
Expand Down
Loading