Skip to content

Commit 2b70610

Browse files
feat(proxy): synthetics cli proxy support (#1044)
* Update: Add proxy support to the cli * Add initial proxy support for push command * Add proxy options to location cmd * Add unit test for proxy settings * Add proxy options to synthetics config, additional unit tests * fix some tests * refactor proxy url in tests * Extend test timeout * Extend test timeout * Extend test timeout * Extend timeout * Test ci * Remove proxy port contention in tests * Update jest.config.js --------- Co-authored-by: Redcinelli <[email protected]>
1 parent f7c89ce commit 2b70610

File tree

11 files changed

+469
-38
lines changed

11 files changed

+469
-38
lines changed

__tests__/locations/index.test.ts

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import { LOCATIONS } from '../fixtures/locationinfo';
3333
import { Server } from '../utils/server';
3434
import { CLIMock } from '../utils/test-config';
3535
import { mkdir, rm, writeFile } from 'fs/promises';
36+
import { Straightforward } from 'straightforward';
37+
import { AddressInfo } from 'net';
3638

3739
describe('Locations', () => {
3840
const apiKey = 'foo';
@@ -82,10 +84,10 @@ describe('Locations', () => {
8284

8385
describe('CLI command', () => {
8486
const PROJECT_DIR = join(__dirname, 'new-project');
85-
async function fakeProjectSetup(settings) {
87+
async function fakeProjectSetup(settings: any) {
8688
await writeFile(
8789
join(PROJECT_DIR, 'synthetics.config.ts'),
88-
`export default { project: ${JSON.stringify(settings)} }`
90+
`export default ${JSON.stringify(settings)}`
8991
);
9092
}
9193

@@ -96,10 +98,13 @@ describe('Locations', () => {
9698
await rm(PROJECT_DIR, { recursive: true, force: true });
9799
});
98100

99-
const runLocations = async (args: Array<string> = []) => {
101+
const runLocations = async (
102+
args: Array<string> = [],
103+
cliEnv: NodeJS.ProcessEnv = {}
104+
) => {
100105
const cli = new CLIMock()
101106
.args(['locations', ...args])
102-
.run({ cwd: PROJECT_DIR, env: process.env });
107+
.run({ cwd: PROJECT_DIR, env: { ...process.env, ...cliEnv } });
103108
expect(await cli.exitCode).toBe(0);
104109
return cli.stderr();
105110
};
@@ -120,10 +125,93 @@ describe('Locations', () => {
120125
});
121126

122127
it('use project url settings for private locations', async () => {
123-
await fakeProjectSetup({ url: server.PREFIX });
128+
await fakeProjectSetup({ project: { url: server.PREFIX } });
124129
const output = await runLocations(['--auth', apiKey]);
125130
expect(output).toContain(`custom location 1`);
126131
expect(output).toContain(`custom location 2`);
127132
});
133+
134+
describe('Proxy options', () => {
135+
let requests: Array<any> = [];
136+
let proxyServer: Straightforward;
137+
let tlsServer: any;
138+
let proxyUrl: string;
139+
140+
beforeAll(async () => {
141+
proxyServer = new Straightforward();
142+
proxyServer.onConnect.use(async ({ req }, next) => {
143+
requests.push(req);
144+
return next();
145+
});
146+
await proxyServer.listen(Math.trunc(65e3 * Math.random()));
147+
tlsServer = await Server.create({ tls: true });
148+
tlsServer.route('/internal/uptime/service/locations', (req, res) => {
149+
res.setHeader('content-type', 'application/json');
150+
res.end(JSON.stringify({ locations: LOCATIONS }));
151+
});
152+
const server = proxyServer.server.address() as AddressInfo;
153+
proxyUrl = `http://127.0.0.1:${server.port}`;
154+
});
155+
156+
afterAll(async () => {
157+
proxyServer?.close();
158+
tlsServer?.close();
159+
});
160+
161+
beforeEach(() => {
162+
requests = [];
163+
});
164+
165+
it('enables proxy based on HTTP_PROXY', async () => {
166+
await fakeProjectSetup({ project: { url: server.PREFIX } });
167+
const output = await runLocations(['--auth', apiKey], {
168+
HTTP_PROXY: proxyUrl,
169+
});
170+
expect(output).toContain(`custom location 1`);
171+
expect(requests).toHaveLength(1);
172+
});
173+
174+
it('honors NO_PROXY with env variables', async () => {
175+
await fakeProjectSetup({ project: { url: server.PREFIX } });
176+
const output = await runLocations(['--auth', apiKey], {
177+
NO_PROXY: '*',
178+
HTTP_PROXY: proxyUrl,
179+
});
180+
expect(output).toContain(`custom location 1`);
181+
expect(requests).toHaveLength(0);
182+
});
183+
184+
it('enables proxy based on HTTPS_PROXY', async () => {
185+
await fakeProjectSetup({ project: { url: tlsServer.PREFIX } });
186+
const output = await runLocations(['--auth', apiKey], {
187+
HTTPS_PROXY: proxyUrl,
188+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
189+
});
190+
expect(output).toContain(`custom location 1`);
191+
expect(requests).toHaveLength(1);
192+
});
193+
194+
it('enables proxy based on --proxy-uri', async () => {
195+
await fakeProjectSetup({ project: { url: server.PREFIX } });
196+
const output = await runLocations([
197+
'--auth',
198+
apiKey,
199+
'--proxy-uri',
200+
proxyUrl,
201+
]);
202+
expect(output).toContain(`custom location 1`);
203+
expect(requests).toHaveLength(1);
204+
});
205+
206+
it('enables proxy based on proxy settings', async () => {
207+
await fakeProjectSetup({
208+
project: { url: server.PREFIX },
209+
proxy: { uri: proxyUrl },
210+
});
211+
const output = await runLocations(['--auth', apiKey]);
212+
expect(output).toContain(`custom location 1`);
213+
expect(requests).toHaveLength(1);
214+
});
215+
});
128216
});
129217
});

__tests__/options.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@
2424
*/
2525

2626
import { CliArgs } from '../src/common_types';
27-
import { normalizeOptions, parsePlaywrightOptions } from '../src/options';
27+
import {
28+
collectOpts,
29+
normalizeOptions,
30+
parseFileOption,
31+
parsePlaywrightOptions,
32+
} from '../src/options';
2833
import { join } from 'path';
2934

3035
describe('options', () => {
@@ -187,4 +192,29 @@ describe('options', () => {
187192
expect(Buffer.isBuffer(t.passphrase)).toBeFalsy();
188193
});
189194
});
195+
196+
describe('parseFileOption', () => {
197+
it('parses file', () => {
198+
expect(
199+
parseFileOption('test')(
200+
join(__dirname, 'fixtures', 'synthetics.config.ts')
201+
)
202+
).toBeInstanceOf(Buffer);
203+
});
204+
it('parses string', () => {
205+
expect(parseFileOption('test')('test')).toEqual('test');
206+
});
207+
});
208+
209+
describe('collectOpts', () => {
210+
it('collects options in the accumulator', () => {
211+
const opts = { a: 'a', b: 'b', c: true };
212+
const result = {};
213+
Object.entries(opts).forEach(([key, value]) => {
214+
collectOpts(key, result)(value);
215+
});
216+
217+
expect(result).toEqual(opts);
218+
});
219+
});
190220
});

__tests__/push/index.test.ts

Lines changed: 164 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { createKibanaTestServer } from '../utils/kibana-test-server';
3232
import { Server } from '../utils/server';
3333
import { CLIMock } from '../utils/test-config';
3434
import { THROTTLING_WARNING_MSG } from '../../src/helpers';
35+
import Straightforward from 'straightforward';
36+
import { AddressInfo } from 'net';
3537

3638
describe('Push', () => {
3739
let server: Server;
@@ -47,8 +49,8 @@ describe('Push', () => {
4749
}
4850

4951
async function fakeProjectSetup(
50-
settings,
51-
monitor,
52+
settings: any,
53+
monitor: any,
5254
filename = 'synthetics.config.ts'
5355
) {
5456
await writeFile(
@@ -353,4 +355,164 @@ heartbeat.monitors:
353355
expect(output).toMatchSnapshot();
354356
});
355357
});
358+
359+
describe('Proxy options', () => {
360+
let requests: Array<any> = [];
361+
let proxyServer: Straightforward;
362+
let tlsServer: any;
363+
let proxyUrl: string;
364+
365+
beforeAll(async () => {
366+
proxyServer = new Straightforward();
367+
proxyServer.onConnect.use(async ({ req }, next) => {
368+
requests.push(req);
369+
return next();
370+
});
371+
await proxyServer.listen(Math.trunc(65e3 * Math.random()));
372+
tlsServer = await createKibanaTestServer('8.6.0', true, (req: any) =>
373+
requests.push(req)
374+
);
375+
const server = proxyServer.server.address() as AddressInfo;
376+
proxyUrl = `http://127.0.0.1:${server.port}`;
377+
});
378+
379+
afterAll(async () => {
380+
proxyServer?.close();
381+
tlsServer?.close();
382+
});
383+
384+
beforeEach(() => {
385+
requests = [];
386+
});
387+
388+
it('enables proxy based on HTTP_PROXY', async () => {
389+
await fakeProjectSetup(
390+
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
391+
{ locations: ['test-loc'], schedule: 3 }
392+
);
393+
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
394+
await writeFile(
395+
testJourney,
396+
`import {journey, monitor} from '../../../';
397+
journey('a', () => monitor.use({ tags: ['chunk'] }));
398+
journey('b', () => monitor.use({ tags: ['chunk'] }));`
399+
);
400+
const output = await runPush([...DEFAULT_ARGS, '--tags', 'chunk'], {
401+
CHUNK_SIZE: '1',
402+
HTTP_PROXY: proxyUrl,
403+
});
404+
await rm(testJourney, { force: true });
405+
expect(output).toContain('Added(2)');
406+
expect(output).toContain('creating or updating 1 monitors');
407+
expect(output).toContain('✓ Pushed:');
408+
expect(requests.length).toBeGreaterThan(0);
409+
});
410+
411+
it('honors NO_PROXY with env variables', async () => {
412+
await fakeProjectSetup(
413+
{ project: { id: 'test-project', space: 'dummy', url: server.PREFIX } },
414+
{ locations: ['test-loc'], schedule: 3 }
415+
);
416+
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
417+
await writeFile(
418+
testJourney,
419+
`import {journey, monitor} from '../../../';
420+
journey('a', () => monitor.use({ tags: ['chunk'] }));
421+
journey('b', () => monitor.use({ tags: ['chunk'] }));`
422+
);
423+
const output = await runPush([...DEFAULT_ARGS, '--tags', 'chunk'], {
424+
CHUNK_SIZE: '1',
425+
HTTP_PROXY: proxyUrl,
426+
NO_PROXY: '*',
427+
});
428+
await rm(testJourney, { force: true });
429+
expect(output).toContain('Added(2)');
430+
expect(output).toContain('creating or updating 1 monitors');
431+
expect(output).toContain('✓ Pushed:');
432+
expect(requests).toHaveLength(0);
433+
});
434+
435+
it('enables proxy based on HTTPS_PROXY', async () => {
436+
await fakeProjectSetup(
437+
{
438+
project: {
439+
id: 'test-project',
440+
space: 'dummy',
441+
url: tlsServer.PREFIX,
442+
},
443+
},
444+
{ locations: ['test-loc'], schedule: 3 }
445+
);
446+
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
447+
await writeFile(
448+
testJourney,
449+
`import {journey, monitor} from '../../../';
450+
journey('a', () => monitor.use({ tags: ['chunk'] }));
451+
journey('b', () => monitor.use({ tags: ['chunk'] }));`
452+
);
453+
const output = await runPush([...DEFAULT_ARGS, '--tags', 'chunk'], {
454+
CHUNK_SIZE: '1',
455+
HTTPS_PROXY: proxyUrl,
456+
NODE_TLS_REJECT_UNAUTHORIZED: '0',
457+
});
458+
await rm(testJourney, { force: true });
459+
expect(output).toContain('Added(2)');
460+
expect(output).toContain('creating or updating 1 monitors');
461+
expect(output).toContain('✓ Pushed:');
462+
expect(requests.length).toBeGreaterThan(0);
463+
});
464+
465+
it('enables proxy based on --proxy-uri', async () => {
466+
await fakeProjectSetup(
467+
{
468+
project: { id: 'test-project', space: 'dummy', url: server.PREFIX },
469+
proxy: { uri: proxyUrl },
470+
},
471+
{ locations: ['test-loc'], schedule: 3 }
472+
);
473+
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
474+
await writeFile(
475+
testJourney,
476+
`import {journey, monitor} from '../../../';
477+
journey('a', () => monitor.use({ tags: ['chunk'] }));
478+
journey('b', () => monitor.use({ tags: ['chunk'] }));`
479+
);
480+
const output = await runPush(
481+
[...DEFAULT_ARGS, '--tags', 'chunk', '--proxy-uri', proxyUrl],
482+
{
483+
CHUNK_SIZE: '1',
484+
}
485+
);
486+
await rm(testJourney, { force: true });
487+
expect(output).toContain('Added(2)');
488+
expect(output).toContain('creating or updating 1 monitors');
489+
expect(output).toContain('✓ Pushed:');
490+
expect(requests.length).toBeGreaterThan(0);
491+
});
492+
493+
it('enables proxy based on proxy settings', async () => {
494+
await fakeProjectSetup(
495+
{
496+
project: { id: 'test-project', space: 'dummy', url: server.PREFIX },
497+
proxy: { uri: proxyUrl },
498+
},
499+
{ locations: ['test-loc'], schedule: 3 }
500+
);
501+
const testJourney = join(PROJECT_DIR, 'chunk.journey.ts');
502+
await writeFile(
503+
testJourney,
504+
`import {journey, monitor} from '../../../';
505+
journey('a', () => monitor.use({ tags: ['chunk'] }));
506+
journey('b', () => monitor.use({ tags: ['chunk'] }));`
507+
);
508+
const output = await runPush([...DEFAULT_ARGS, '--tags', 'chunk'], {
509+
CHUNK_SIZE: '1',
510+
});
511+
await rm(testJourney, { force: true });
512+
expect(output).toContain('Added(2)');
513+
expect(output).toContain('creating or updating 1 monitors');
514+
expect(output).toContain('✓ Pushed:');
515+
expect(requests.length).toBeGreaterThan(0);
516+
});
517+
});
356518
});

0 commit comments

Comments
 (0)