324 lines
14 KiB
TypeScript
324 lines
14 KiB
TypeScript
import { ProxyConfiguration, Request } from '@crawlee/core';
|
|
|
|
const sessionId = 538909250932;
|
|
|
|
describe('ProxyConfiguration', () => {
|
|
test('newUrl() should return proxy URL', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({ proxyUrls: ['http://proxy.com:1111'] });
|
|
expect(proxyConfiguration).toBeInstanceOf(ProxyConfiguration);
|
|
expect(await proxyConfiguration.newUrl(sessionId)).toBe('http://proxy.com:1111');
|
|
});
|
|
|
|
test('newProxyInfo() should return ProxyInfo object', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({ proxyUrls: ['http://proxy.com:1111'] });
|
|
const url = 'http://proxy.com:1111';
|
|
|
|
const proxyInfo = {
|
|
sessionId: `${sessionId}`,
|
|
url,
|
|
hostname: 'proxy.com',
|
|
username: '',
|
|
password: '',
|
|
port: '1111',
|
|
};
|
|
expect(await proxyConfiguration.newProxyInfo(sessionId)).toEqual(proxyInfo);
|
|
});
|
|
|
|
test('newProxyInfo() works with special characters', async () => {
|
|
const url = 'http://user%40name:pass%40word@proxy.com:1111';
|
|
const proxyConfiguration = new ProxyConfiguration({ proxyUrls: [url] });
|
|
|
|
const proxyInfo = {
|
|
sessionId: `${sessionId}`,
|
|
url,
|
|
hostname: 'proxy.com',
|
|
username: 'user@name',
|
|
password: 'pass@word',
|
|
port: '1111',
|
|
};
|
|
expect(await proxyConfiguration.newProxyInfo(sessionId)).toEqual(proxyInfo);
|
|
});
|
|
|
|
test('should throw on invalid newUrlFunction', async () => {
|
|
const newUrlFunction = () => {
|
|
return 'http://proxy.com:1111*invalid_url';
|
|
};
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
newUrlFunction,
|
|
});
|
|
try {
|
|
await proxyConfiguration.newUrl();
|
|
throw new Error('wrong error');
|
|
} catch (err) {
|
|
expect((err as Error).message).toMatch('The provided newUrlFunction did not return');
|
|
}
|
|
});
|
|
|
|
test('newUrlFunction should correctly generate URLs', async () => {
|
|
const customUrls = [
|
|
'http://proxy.com:1111',
|
|
'http://proxy.com:2222',
|
|
'http://proxy.com:3333',
|
|
'http://proxy.com:4444',
|
|
'http://proxy.com:5555',
|
|
'http://proxy.com:6666',
|
|
];
|
|
const newUrlFunction = () => {
|
|
return customUrls.pop() ?? null;
|
|
};
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
newUrlFunction,
|
|
});
|
|
|
|
// through newUrl()
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:6666');
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:5555');
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:4444');
|
|
|
|
// through newProxyInfo()
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:3333');
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:2222');
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:1111');
|
|
});
|
|
|
|
test('async newUrlFunction should work correctly', async () => {
|
|
const customUrls = [
|
|
'http://proxy.com:1111',
|
|
'http://proxy.com:2222',
|
|
'http://proxy.com:3333',
|
|
'http://proxy.com:4444',
|
|
'http://proxy.com:5555',
|
|
'http://proxy.com:6666',
|
|
];
|
|
const newUrlFunction = async () => {
|
|
await new Promise((r) => setTimeout(r, 5));
|
|
return customUrls.pop() ?? null;
|
|
};
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
newUrlFunction,
|
|
});
|
|
|
|
// through newUrl()
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:6666');
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:5555');
|
|
expect(await proxyConfiguration.newUrl()).toEqual('http://proxy.com:4444');
|
|
|
|
// through newProxyInfo()
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:3333');
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:2222');
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual('http://proxy.com:1111');
|
|
});
|
|
|
|
describe('With proxyUrls options', () => {
|
|
test('should rotate custom URLs correctly', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls: ['http://proxy.com:1111', 'http://proxy.com:2222', 'http://proxy.com:3333'],
|
|
});
|
|
|
|
// @ts-expect-error private property
|
|
const proxyUrls = proxyConfiguration.proxyUrls!;
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[0]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[1]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[2]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[0]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[1]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(proxyUrls[2]);
|
|
});
|
|
|
|
test('newProxyInfo() should return correctly rotated URL', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls: ['http://proxy.com:1111', 'http://proxy.com:2222', 'http://proxy.com:3333'],
|
|
});
|
|
|
|
// @ts-expect-error TODO private property?
|
|
const proxyUrls = proxyConfiguration.proxyUrls!;
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[0]);
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[1]);
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[2]);
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[0]);
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[1]);
|
|
expect((await proxyConfiguration.newProxyInfo())!.url).toEqual(proxyUrls[2]);
|
|
});
|
|
|
|
test('should rotate custom URLs with sessions correctly', async () => {
|
|
const sessions = ['session_01', 'session_02', 'session_03', 'session_04', 'session_05', 'session_06'];
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls: ['http://proxy.com:1111', 'http://proxy.com:2222', 'http://proxy.com:3333'],
|
|
});
|
|
|
|
// @ts-expect-error TODO private property?
|
|
const proxyUrls = proxyConfiguration.proxyUrls!;
|
|
// should use same proxy URL
|
|
expect(await proxyConfiguration.newUrl(sessions[0])).toEqual(proxyUrls[0]);
|
|
expect(await proxyConfiguration.newUrl(sessions[0])).toEqual(proxyUrls[0]);
|
|
expect(await proxyConfiguration.newUrl(sessions[0])).toEqual(proxyUrls[0]);
|
|
|
|
// should rotate different proxies
|
|
expect(await proxyConfiguration.newUrl(sessions[1])).toEqual(proxyUrls[1]);
|
|
expect(await proxyConfiguration.newUrl(sessions[2])).toEqual(proxyUrls[2]);
|
|
expect(await proxyConfiguration.newUrl(sessions[3])).toEqual(proxyUrls[0]);
|
|
expect(await proxyConfiguration.newUrl(sessions[4])).toEqual(proxyUrls[1]);
|
|
expect(await proxyConfiguration.newUrl(sessions[5])).toEqual(proxyUrls[2]);
|
|
|
|
// should remember already used session
|
|
expect(await proxyConfiguration.newUrl(sessions[1])).toEqual(proxyUrls[1]);
|
|
expect(await proxyConfiguration.newUrl(sessions[3])).toEqual(proxyUrls[0]);
|
|
});
|
|
|
|
test('should throw cannot combine custom methods', async () => {
|
|
const proxyUrls = ['http://proxy.com:1111', 'http://proxy.com:2222', 'http://proxy.com:3333'];
|
|
const newUrlFunction = () => {
|
|
return proxyUrls[Math.floor(Math.random() * proxyUrls.length)];
|
|
};
|
|
try {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls,
|
|
newUrlFunction,
|
|
});
|
|
throw new Error('wrong error');
|
|
} catch (err) {
|
|
expect((err as Error).message).toMatch('Cannot combine custom proxies "options.proxyUrls"');
|
|
}
|
|
});
|
|
|
|
test('should throw proxyUrls array is empty', async () => {
|
|
try {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls: [],
|
|
});
|
|
throw new Error('wrong error');
|
|
} catch (err) {
|
|
expect((err as Error).message).toMatch('Expected property array `proxyUrls` to not be empty');
|
|
}
|
|
});
|
|
|
|
test('should throw invalid custom URL form', async () => {
|
|
try {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
proxyUrls: ['http://proxy.com:1111*invalid_url'],
|
|
});
|
|
throw new Error('wrong error');
|
|
} catch (err) {
|
|
expect((err as Error).message).toMatch('to be a URL, got `http://proxy.com:1111*invalid_url`');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('with tieredProxyUrls', () => {
|
|
test('without Request rotates the urls uniformly', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
tieredProxyUrls: [
|
|
['http://proxy.com:1111', 'http://proxy.com:2222'],
|
|
['http://proxy.com:3333', 'http://proxy.com:4444'],
|
|
],
|
|
});
|
|
|
|
// @ts-expect-error protected property
|
|
const tieredProxyUrls = proxyConfiguration.tieredProxyUrls!;
|
|
expect(await proxyConfiguration.newUrl()).toEqual(tieredProxyUrls[0][0]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(tieredProxyUrls[0][1]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(tieredProxyUrls[1][0]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(tieredProxyUrls[1][1]);
|
|
expect(await proxyConfiguration.newUrl()).toEqual(tieredProxyUrls[0][0]);
|
|
});
|
|
|
|
test('rotating a request results in higher-level proxies', async () => {
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
tieredProxyUrls: [['http://proxy.com:1111'], ['http://proxy.com:2222'], ['http://proxy.com:3333']],
|
|
});
|
|
|
|
const request = new Request({
|
|
url: 'http://example.com',
|
|
});
|
|
|
|
// @ts-expect-error protected property
|
|
const tieredProxyUrls = proxyConfiguration.tieredProxyUrls!;
|
|
expect(await proxyConfiguration.newUrl('session-id', { request })).toEqual(tieredProxyUrls[0][0]);
|
|
expect(await proxyConfiguration.newUrl('session-id', { request })).toEqual(tieredProxyUrls[1][0]);
|
|
expect(await proxyConfiguration.newUrl('session-id', { request })).toEqual(tieredProxyUrls[2][0]);
|
|
|
|
// we still get the same (higher) proxy tier even with a new request
|
|
const request2 = new Request({
|
|
url: 'http://example.com/another-resource',
|
|
});
|
|
|
|
expect(await proxyConfiguration.newUrl('session-id', { request: request2 })).toEqual(tieredProxyUrls[2][0]);
|
|
});
|
|
|
|
test('upshifts and downshifts properly', async () => {
|
|
const tieredProxyUrls = [['http://proxy.com:1111'], ['http://proxy.com:2222'], ['http://proxy.com:3333']];
|
|
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
tieredProxyUrls,
|
|
});
|
|
|
|
const request = new Request({
|
|
url: 'http://example.com',
|
|
});
|
|
|
|
let gotToTheHighestProxy = false;
|
|
for (let i = 0; i < 10; i++) {
|
|
const lastProxyUrl = await proxyConfiguration.newUrl('session-id', { request });
|
|
if (lastProxyUrl === tieredProxyUrls[2][0]) {
|
|
gotToTheHighestProxy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(gotToTheHighestProxy).toBe(true);
|
|
|
|
// Even the highest-tier proxies didn't help - we should try going down
|
|
let gotToTheLowestProxy = false;
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
const lastProxyUrl = await proxyConfiguration.newUrl('session-id', { request });
|
|
if (lastProxyUrl === tieredProxyUrls[0][0]) {
|
|
gotToTheLowestProxy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(gotToTheLowestProxy).toBe(true);
|
|
});
|
|
|
|
test('successful requests make the proxy tier drop eventually', async () => {
|
|
const tieredProxyUrls = [['http://proxy.com:1111'], ['http://proxy.com:2222'], ['http://proxy.com:3333']];
|
|
|
|
const proxyConfiguration = new ProxyConfiguration({
|
|
tieredProxyUrls,
|
|
});
|
|
|
|
const failingRequest = new Request({
|
|
url: 'http://example.com',
|
|
});
|
|
let gotToTheHighestProxy = false;
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
const lastProxyUrl = await proxyConfiguration.newUrl('session-id', { request: failingRequest });
|
|
|
|
if (lastProxyUrl === tieredProxyUrls[2][0]) {
|
|
gotToTheHighestProxy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(gotToTheHighestProxy).toBe(true);
|
|
|
|
let gotToTheLowestProxy = false;
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
const lastProxyUrl = await proxyConfiguration.newUrl('session-id', {
|
|
request: new Request({ url: `http://example.com/${i}` }),
|
|
});
|
|
|
|
if (lastProxyUrl === tieredProxyUrls[0][0]) {
|
|
gotToTheLowestProxy = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(gotToTheLowestProxy).toBe(true);
|
|
});
|
|
});
|
|
});
|