From 7fb86b88e8127c78f9e9ec89a41b45c0c91e2444 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 27 Feb 2026 07:37:23 +0100 Subject: [PATCH] test(charging-station): add WebSocket Message Handling tests Add 18 WebSocket tests covering connection management, message capture, event simulation, and edge cases. All tests passing (197 total). --- .../charging-station/ChargingStation.test.ts | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) diff --git a/tests/charging-station/ChargingStation.test.ts b/tests/charging-station/ChargingStation.test.ts index 43ec2967..3bce6a28 100644 --- a/tests/charging-station/ChargingStation.test.ts +++ b/tests/charging-station/ChargingStation.test.ts @@ -665,4 +665,323 @@ await describe('ChargingStation', async () => { expect(mocks.fileSystem.writtenFiles.size).toBe(0) }) }) + + await describe('WebSocket Message Handling', async () => { + let station: ChargingStation | undefined + + afterEach(() => { + if (station != null) { + cleanupChargingStation(station) + } + }) + + // === Connection Management Tests === + + await it('should report WebSocket connection state via isWebSocketConnectionOpened()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Assert - connection is open by default + expect(station.isWebSocketConnectionOpened()).toBe(true) + + // Act - change ready state to CLOSED + mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED + + // Assert + expect(station.isWebSocketConnectionOpened()).toBe(false) + }) + + await it('should return false when WebSocket is CONNECTING', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Act + mocks.webSocket.readyState = 0 // WebSocketReadyState.CONNECTING + + // Assert + expect(station.isWebSocketConnectionOpened()).toBe(false) + }) + + await it('should return false when WebSocket is CLOSING', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Act + mocks.webSocket.readyState = 2 // WebSocketReadyState.CLOSING + + // Assert + expect(station.isWebSocketConnectionOpened()).toBe(false) + }) + + await it('should close WebSocket connection via closeWSConnection()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + + // Assert - connection exists initially + expect(station.wsConnection).not.toBeNull() + + // Act + station.closeWSConnection() + + // Assert - connection is nullified + expect(station.wsConnection).toBeNull() + }) + + await it('should handle closeWSConnection() when already closed', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + + // Act - close twice (idempotent) + station.closeWSConnection() + station.closeWSConnection() + + // Assert - no error, connection remains null + expect(station.wsConnection).toBeNull() + }) + + // === Message Capture Tests === + + await it('should capture sent messages in sentMessages array', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Act - send messages via mock WebSocket + mocks.webSocket.send('["2","uuid-1","Heartbeat",{}]') + mocks.webSocket.send('["2","uuid-2","StatusNotification",{"connectorId":1}]') + + // Assert + expect(mocks.webSocket.sentMessages.length).toBe(2) + expect(mocks.webSocket.sentMessages[0]).toBe('["2","uuid-1","Heartbeat",{}]') + expect(mocks.webSocket.sentMessages[1]).toBe( + '["2","uuid-2","StatusNotification",{"connectorId":1}]' + ) + }) + + await it('should return last sent message via getLastSentMessage()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Act + mocks.webSocket.send('["2","uuid-1","Heartbeat",{}]') + mocks.webSocket.send('["2","uuid-2","BootNotification",{}]') + + // Assert + expect(mocks.webSocket.getLastSentMessage()).toBe('["2","uuid-2","BootNotification",{}]') + }) + + await it('should return undefined for getLastSentMessage() when no messages sent', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Assert + expect(mocks.webSocket.getLastSentMessage()).toBeUndefined() + }) + + await it('should parse sent messages as JSON via getSentMessagesAsJson()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Act + mocks.webSocket.send('[2,"uuid-1","Heartbeat",{}]') + + // Assert + const parsed = mocks.webSocket.getSentMessagesAsJson() + expect(parsed.length).toBe(1) + expect(parsed[0]).toEqual([2, 'uuid-1', 'Heartbeat', {}]) + }) + + await it('should clear captured messages via clearMessages()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Populate messages + mocks.webSocket.send('["2","uuid-1","Heartbeat",{}]') + mocks.webSocket.send('["2","uuid-2","Heartbeat",{}]') + expect(mocks.webSocket.sentMessages.length).toBe(2) + + // Act + mocks.webSocket.clearMessages() + + // Assert + expect(mocks.webSocket.sentMessages.length).toBe(0) + expect(mocks.webSocket.sentBinaryMessages.length).toBe(0) + }) + + // === Event Simulation Tests === + + await it('should emit message event via simulateMessage()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let receivedData: unknown + + // Set up listener + mocks.webSocket.on('message', (data: unknown) => { + receivedData = data + }) + + // Act + mocks.webSocket.simulateMessage('[3,"uuid-1",{}]') + + // Assert + expect(receivedData).toBeDefined() + expect(Buffer.isBuffer(receivedData)).toBe(true) + }) + + await it('should emit open event and set readyState via simulateOpen()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let openEventFired = false + + // First close the connection to test opening + mocks.webSocket.readyState = 3 // CLOSED + + // Set up listener + mocks.webSocket.on('open', () => { + openEventFired = true + }) + + // Act + mocks.webSocket.simulateOpen() + + // Assert + expect(openEventFired).toBe(true) + expect(mocks.webSocket.readyState).toBe(1) // WebSocketReadyState.OPEN + }) + + await it('should emit close event and set readyState via simulateClose()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let closeCode: number | undefined + + // Set up listener + mocks.webSocket.on('close', (code: number) => { + closeCode = code + }) + + // Act + mocks.webSocket.simulateClose(1001, 'Going away') + + // Assert + expect(closeCode).toBe(1001) + expect(mocks.webSocket.readyState).toBe(3) // WebSocketReadyState.CLOSED + }) + + await it('should emit error event via simulateError()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let receivedError: Error | undefined + + // Set up listener + mocks.webSocket.on('error', (error: Error) => { + receivedError = error + }) + + // Act + const testError = new Error('Connection refused') + mocks.webSocket.simulateError(testError) + + // Assert + expect(receivedError).toBe(testError) + expect(receivedError?.message).toBe('Connection refused') + }) + + await it('should emit ping event via simulatePing()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let pingReceived = false + let pingData: Buffer | undefined + + // Set up listener + mocks.webSocket.on('ping', (data: Buffer) => { + pingReceived = true + pingData = data + }) + + // Act + mocks.webSocket.simulatePing(Buffer.from('ping-data')) + + // Assert + expect(pingReceived).toBe(true) + expect(pingData?.toString()).toBe('ping-data') + }) + + await it('should emit pong event via simulatePong()', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + let pongReceived = false + let pongData: Buffer | undefined + + // Set up listener + mocks.webSocket.on('pong', (data: Buffer) => { + pongReceived = true + pongData = data + }) + + // Act + mocks.webSocket.simulatePong(Buffer.from('pong-data')) + + // Assert + expect(pongReceived).toBe(true) + expect(pongData?.toString()).toBe('pong-data') + }) + + // === Edge Case Tests === + + await it('should throw error when sending on closed WebSocket', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Close the WebSocket + mocks.webSocket.readyState = 3 // WebSocketReadyState.CLOSED + + // Act & Assert + expect(() => { + mocks.webSocket.send('["2","uuid","Heartbeat",{}]') + }).toThrow('WebSocket is not open') + }) + + await it('should capture URL from WebSocket connection', () => { + // Arrange + const result = createMockChargingStation({ connectorsCount: 1 }) + station = result.station + const mocks = result.mocks + + // Assert + expect(mocks.webSocket.url).toBeDefined() + expect(typeof mocks.webSocket.url).toBe('string') + expect(mocks.webSocket.url.length).toBeGreaterThan(0) + }) + }) }) -- 2.53.0