test: add comprehensive WebRTC test suite
Add test coverage for WebRTC signaling and detection: Backend tests (socket-webrtc.test.js): - WebRTC offer/answer relay via Socket.IO - ICE candidate exchange - Authorization checks for match access - Full WebRTC signaling flow - All 7 tests passing Frontend tests (ready for test runner): - webrtcDetection.test.js: Browser WebRTC capability detection - WebRTCWarning.test.jsx: Warning component rendering and interaction Note: Frontend tests require test runner setup (e.g., Vitest)
This commit is contained in:
210
frontend/src/utils/__tests__/webrtcDetection.test.js
Normal file
210
frontend/src/utils/__tests__/webrtcDetection.test.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import { detectWebRTCSupport, getWebRTCErrorMessage, getWebRTCFixSuggestions } from '../webrtcDetection';
|
||||
|
||||
describe('WebRTC Detection', () => {
|
||||
let mockRTCPeerConnection;
|
||||
let mockCreateDataChannel;
|
||||
let mockCreateOffer;
|
||||
let mockSetLocalDescription;
|
||||
let mockOnIceCandidate;
|
||||
let mockClose;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock RTCPeerConnection
|
||||
mockCreateDataChannel = jest.fn();
|
||||
mockCreateOffer = jest.fn();
|
||||
mockSetLocalDescription = jest.fn();
|
||||
mockClose = jest.fn();
|
||||
|
||||
mockRTCPeerConnection = jest.fn(function() {
|
||||
this.createDataChannel = mockCreateDataChannel;
|
||||
this.createOffer = mockCreateOffer;
|
||||
this.setLocalDescription = mockSetLocalDescription;
|
||||
this.close = mockClose;
|
||||
this.onicecandidate = null;
|
||||
});
|
||||
|
||||
global.RTCPeerConnection = mockRTCPeerConnection;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete global.RTCPeerConnection;
|
||||
});
|
||||
|
||||
describe('detectWebRTCSupport', () => {
|
||||
it('should detect when RTCPeerConnection is not available', async () => {
|
||||
delete global.RTCPeerConnection;
|
||||
|
||||
const result = await detectWebRTCSupport();
|
||||
|
||||
expect(result.supported).toBe(false);
|
||||
expect(result.hasIceCandidates).toBe(false);
|
||||
expect(result.error).toContain('not available');
|
||||
});
|
||||
|
||||
it('should detect when ICE candidates are generated successfully', async () => {
|
||||
// Mock successful ICE candidate generation
|
||||
mockCreateOffer.mockResolvedValue({ type: 'offer', sdp: 'mock-sdp' });
|
||||
mockSetLocalDescription.mockResolvedValue();
|
||||
|
||||
const promise = detectWebRTCSupport();
|
||||
|
||||
// Simulate ICE candidate event after a short delay
|
||||
setTimeout(() => {
|
||||
const pc = mockRTCPeerConnection.mock.results[0].value;
|
||||
if (pc.onicecandidate) {
|
||||
// Simulate candidate
|
||||
pc.onicecandidate({ candidate: { type: 'host', candidate: 'mock-candidate' } });
|
||||
// Simulate gathering complete
|
||||
pc.onicecandidate({ candidate: null });
|
||||
}
|
||||
}, 50);
|
||||
|
||||
const result = await promise;
|
||||
|
||||
expect(result.supported).toBe(true);
|
||||
expect(result.hasIceCandidates).toBe(true);
|
||||
expect(result.error).toBeNull();
|
||||
expect(mockCreateDataChannel).toHaveBeenCalledWith('test');
|
||||
expect(mockCreateOffer).toHaveBeenCalled();
|
||||
expect(mockSetLocalDescription).toHaveBeenCalled();
|
||||
expect(mockClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect when ICE candidates are NOT generated (blocked)', async () => {
|
||||
mockCreateOffer.mockResolvedValue({ type: 'offer', sdp: 'mock-sdp' });
|
||||
mockSetLocalDescription.mockResolvedValue();
|
||||
|
||||
const promise = detectWebRTCSupport();
|
||||
|
||||
// Simulate only gathering complete, no actual candidates
|
||||
setTimeout(() => {
|
||||
const pc = mockRTCPeerConnection.mock.results[0].value;
|
||||
if (pc.onicecandidate) {
|
||||
pc.onicecandidate({ candidate: null }); // Only null = gathering complete
|
||||
}
|
||||
}, 50);
|
||||
|
||||
const result = await promise;
|
||||
|
||||
expect(result.supported).toBe(true);
|
||||
expect(result.hasIceCandidates).toBe(false);
|
||||
expect(result.error).toContain('ICE candidates not generated');
|
||||
});
|
||||
|
||||
it('should handle timeout when no ICE events occur', async () => {
|
||||
mockCreateOffer.mockResolvedValue({ type: 'offer', sdp: 'mock-sdp' });
|
||||
mockSetLocalDescription.mockResolvedValue();
|
||||
|
||||
// Don't trigger any ICE events - let it timeout
|
||||
const result = await detectWebRTCSupport();
|
||||
|
||||
expect(result.supported).toBe(true);
|
||||
expect(result.hasIceCandidates).toBe(false);
|
||||
expect(mockClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle errors during detection', async () => {
|
||||
mockCreateOffer.mockRejectedValue(new Error('Mock error'));
|
||||
|
||||
const result = await detectWebRTCSupport();
|
||||
|
||||
expect(result.supported).toBe(true);
|
||||
expect(result.hasIceCandidates).toBe(false);
|
||||
expect(result.error).toContain('Mock error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWebRTCErrorMessage', () => {
|
||||
it('should return correct message when not supported', () => {
|
||||
const detection = {
|
||||
supported: false,
|
||||
hasIceCandidates: false,
|
||||
error: 'Not available',
|
||||
};
|
||||
|
||||
const message = getWebRTCErrorMessage(detection);
|
||||
|
||||
expect(message).toContain('does not support WebRTC');
|
||||
expect(message).toContain('modern browser');
|
||||
});
|
||||
|
||||
it('should return correct message when ICE candidates blocked', () => {
|
||||
const detection = {
|
||||
supported: true,
|
||||
hasIceCandidates: false,
|
||||
error: 'ICE candidates not generated',
|
||||
};
|
||||
|
||||
const message = getWebRTCErrorMessage(detection);
|
||||
|
||||
expect(message).toContain('WebRTC is blocked');
|
||||
expect(message).toContain('browser settings');
|
||||
});
|
||||
|
||||
it('should return success message when working correctly', () => {
|
||||
const detection = {
|
||||
supported: true,
|
||||
hasIceCandidates: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const message = getWebRTCErrorMessage(detection);
|
||||
|
||||
expect(message).toContain('working correctly');
|
||||
});
|
||||
|
||||
it('should return generic error message when there is an unknown error', () => {
|
||||
const detection = {
|
||||
supported: true,
|
||||
hasIceCandidates: false,
|
||||
error: 'Unknown error occurred',
|
||||
};
|
||||
|
||||
const message = getWebRTCErrorMessage(detection);
|
||||
|
||||
expect(message).toContain('Unknown error occurred');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWebRTCFixSuggestions', () => {
|
||||
it('should return browser update suggestions when not supported', () => {
|
||||
const detection = {
|
||||
supported: false,
|
||||
hasIceCandidates: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const suggestions = getWebRTCFixSuggestions(detection);
|
||||
|
||||
expect(suggestions).toContain('Update your browser to the latest version');
|
||||
expect(suggestions).toContain('Try using Chrome, Firefox, or Edge');
|
||||
});
|
||||
|
||||
it('should return privacy settings suggestions when ICE candidates blocked', () => {
|
||||
const detection = {
|
||||
supported: true,
|
||||
hasIceCandidates: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const suggestions = getWebRTCFixSuggestions(detection);
|
||||
|
||||
expect(suggestions.some(s => s.includes('privacy settings'))).toBe(true);
|
||||
expect(suggestions.some(s => s.includes('VPN'))).toBe(true);
|
||||
expect(suggestions.some(s => s.includes('incognito'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return empty array when working correctly', () => {
|
||||
const detection = {
|
||||
supported: true,
|
||||
hasIceCandidates: true,
|
||||
error: null,
|
||||
};
|
||||
|
||||
const suggestions = getWebRTCFixSuggestions(detection);
|
||||
|
||||
expect(suggestions).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user