/** * Tests for network chaos: applyNetworkCondition (via PlaywrightAgent private method) * and route interception for blocked/slow endpoints. * * Since applyNetworkChaos is private, we test it indirectly via PlaywrightAgent * by mocking the CDP session and route handler. */ import { NETWORK_PROFILES } from '../../src/core/ExplorationConfig'; // ─── NETWORK_PROFILES constants ─────────────────────────────────────────────── describe('NETWORK_PROFILES', () => { it('defines fast-3g profile', () => { const p = NETWORK_PROFILES['fast-3g']; expect(p).toBeDefined(); expect(p!.offline).toBe(false); expect(p!.downloadKbps).toBe(1500); expect(p!.latencyMs).toBe(40); }); it('defines slow-3g profile', () => { const p = NETWORK_PROFILES['slow-3g']; expect(p).toBeDefined(); expect(p!.downloadKbps).toBe(400); expect(p!.latencyMs).toBe(400); }); it('defines 2g profile', () => { const p = NETWORK_PROFILES['2g']; expect(p).toBeDefined(); expect(p!.downloadKbps).toBe(50); expect(p!.latencyMs).toBe(800); }); it('defines offline profile', () => { const p = NETWORK_PROFILES['offline']; expect(p).toBeDefined(); expect(p!.offline).toBe(true); expect(p!.downloadKbps).toBe(0); }); it('returns null for "none" profile', () => { expect(NETWORK_PROFILES['none']).toBeNull(); }); it('covers all expected profile keys', () => { const expected = ['fast-3g', 'slow-3g', '2g', 'offline', 'none'] as const; for (const key of expected) { expect(NETWORK_PROFILES).toHaveProperty(key); } }); }); // ─── applyNetworkCondition via mocked CDP ───────────────────────────────────── describe('PlaywrightAgent network chaos via CDP mock', () => { const cdpSendMock = jest.fn().mockResolvedValue(undefined); const routeMock = jest.fn().mockResolvedValue(undefined); const newCDPSessionMock = jest.fn().mockResolvedValue({ send: cdpSendMock }); function buildMockContext() { return { newCDPSession: newCDPSessionMock }; } function buildMockPage(url = 'http://localhost') { return { url: () => url, route: routeMock, goto: jest.fn().mockResolvedValue(undefined), waitForTimeout: jest.fn().mockResolvedValue(undefined), evaluate: jest.fn().mockResolvedValue(''), title: jest.fn().mockResolvedValue('Test'), on: jest.fn(), }; } beforeEach(() => { cdpSendMock.mockClear(); routeMock.mockClear(); newCDPSessionMock.mockClear(); }); it('calls CDP Network.emulateNetworkConditions for fast-3g', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'fast-3g', blockedEndpoints: [], slowEndpoints: [], }, }, }); // Inject mocked internals // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const page = buildMockPage(); // Call applyNetworkChaos via a private method trick // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(newCDPSessionMock).toHaveBeenCalledWith(page); expect(cdpSendMock).toHaveBeenCalledWith('Network.emulateNetworkConditions', expect.objectContaining({ offline: false, latency: 40, })); }); it('calls CDP with offline:true for offline profile', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'offline', blockedEndpoints: [], slowEndpoints: [], }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); const page = buildMockPage(); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(cdpSendMock).toHaveBeenCalledWith('Network.emulateNetworkConditions', expect.objectContaining({ offline: true, downloadThroughput: -1, uploadThroughput: -1, })); }); it('sets up route interception for blocked endpoints', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'none', blockedEndpoints: ['*/api/analytics'], slowEndpoints: [], }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); const page = buildMockPage(); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(routeMock).toHaveBeenCalledWith('**/*', expect.any(Function)); }); it('sets up route interception for slow endpoints', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'none', blockedEndpoints: [], slowEndpoints: [{ pattern: '*/api/slow', delayMs: 2000 }], }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); const page = buildMockPage(); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(routeMock).toHaveBeenCalledWith('*/api/slow', expect.any(Function)); }); it('does nothing when networkChaos is disabled', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: false, profile: 'fast-3g', blockedEndpoints: [], slowEndpoints: [] }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); const page = buildMockPage(); // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(cdpSendMock).not.toHaveBeenCalled(); expect(routeMock).not.toHaveBeenCalled(); }); it('fulfills with 503 for requests matching blocked endpoint patterns', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'none', blockedEndpoints: ['*/api/blocked'], slowEndpoints: [], }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); // Capture the route handler let capturedHandler: ((route: unknown) => void) | null = null; const page = { ...buildMockPage(), route: jest.fn().mockImplementation((_pattern: unknown, handler: (r: unknown) => void) => { capturedHandler = handler; }), }; // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); expect(capturedHandler).not.toBeNull(); // Simulate a request to a blocked URL const fulfillMock = jest.fn(); const continueMock = jest.fn(); const blockedRoute = { request: () => ({ url: () => 'http://example.com/api/blocked' }), fulfill: fulfillMock, continue: continueMock, }; capturedHandler!(blockedRoute); expect(fulfillMock).toHaveBeenCalledWith(expect.objectContaining({ status: 503 })); expect(continueMock).not.toHaveBeenCalled(); }); it('continues requests that do not match blocked patterns', async () => { const { PlaywrightAgent } = await import('../../src/plugins/agents/PlaywrightAgent'); const agent = new PlaywrightAgent({ explorationConfig: { networkChaos: { enabled: true, profile: 'none', blockedEndpoints: ['*/api/blocked'], slowEndpoints: [], }, }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any (agent as any).context = buildMockContext(); let capturedHandler: ((route: unknown) => void) | null = null; const page = { ...buildMockPage(), route: jest.fn().mockImplementation((_pattern: unknown, handler: (r: unknown) => void) => { capturedHandler = handler; }), }; // eslint-disable-next-line @typescript-eslint/no-explicit-any await (agent as any).applyNetworkChaos(page); const fulfillMock = jest.fn(); const continueMock = jest.fn(); const allowedRoute = { request: () => ({ url: () => 'http://example.com/api/users' }), fulfill: fulfillMock, continue: continueMock, }; capturedHandler!(allowedRoute); expect(continueMock).toHaveBeenCalled(); expect(fulfillMock).not.toHaveBeenCalled(); }); });