290 lines
9.4 KiB
TypeScript
290 lines
9.4 KiB
TypeScript
/**
|
|
* 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('<body></body>'),
|
|
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();
|
|
});
|
|
});
|