Skip to content

Commit 2a9e7bf

Browse files
committed
Add an automated test for PHP-Apache-Docker interception
1 parent fccb8b6 commit 2a9e7bf

File tree

3 files changed

+99
-18
lines changed

3 files changed

+99
-18
lines changed

test/fixtures/docker/php/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM php:8.0-apache
2+
3+
WORKDIR /var/www/html
4+
5+
COPY index.php index.php

test/fixtures/docker/php/index.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Hello world
2+
3+
<?php
4+
5+
$stdout = fopen('php://stdout', 'w');
6+
7+
fwrite($stdout, 'Running PHP');
8+
9+
while (true) {
10+
$cURL = curl_init();
11+
$setopt_array = array(
12+
CURLOPT_URL => $_GET['target'],
13+
CURLOPT_RETURNTRANSFER => true,
14+
CURLOPT_HTTPHEADER => array()
15+
);
16+
curl_setopt_array($cURL, $setopt_array);
17+
curl_exec($cURL);
18+
$httpcode = curl_getinfo($cURL, CURLINFO_HTTP_CODE);
19+
fwrite($stdout, "Got ".$httpcode." response");
20+
curl_close($cURL);
21+
22+
sleep(1);
23+
}
24+
25+
?>

test/interceptors/docker-attachment.spec.ts

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,23 @@ import * as fs from 'fs';
55
import { expect } from 'chai';
66

77
import * as Docker from 'dockerode';
8+
import fetch from 'node-fetch';
89

910
import { setupInterceptor, itIsAvailable } from './interceptor-test-utils';
1011
import { delay } from '../../src/util/promise';
1112

1213
const docker = new Docker();
1314
const DOCKER_FIXTURES = path.join(__dirname, '..', 'fixtures', 'docker');
1415

15-
async function buildAndRun(dockerFolder: string, argument: string) {
16+
async function buildAndRun(dockerFolder: string, options: {
17+
arguments?: string[],
18+
attach?: boolean,
19+
portBindings?: { [key: string]: {} },
20+
} = {}) {
21+
const shouldAttach = options.attach ?? true;
22+
const containerArguments = options.arguments ?? [];
23+
const portBindings = options.portBindings ?? {};
24+
1625
const imageName = `test-${dockerFolder}:latest`;
1726

1827
const dockerContext = {
@@ -46,27 +55,38 @@ async function buildAndRun(dockerFolder: string, argument: string) {
4655
// Run the container, using its default entrypoint
4756
const container = await docker.createContainer({
4857
Image: imageName,
49-
HostConfig: { AutoRemove: true },
50-
Cmd: [argument],
51-
Tty: true,
58+
HostConfig: {
59+
AutoRemove: true,
60+
PortBindings: portBindings
61+
},
62+
Cmd: containerArguments,
63+
Tty: shouldAttach
5264
});
5365

54-
const stream = await container.attach({
55-
stream: true,
56-
stdout: true,
57-
stderr: true
58-
});
66+
let startupPromise: Promise<void>
67+
if (shouldAttach) {
68+
const stream = await container.attach({
69+
stream: true,
70+
stdout: true,
71+
stderr: true
72+
});
5973

60-
stream.setEncoding('utf8');
61-
stream.pipe(process.stdout);
74+
stream.setEncoding('utf8');
75+
stream.pipe(process.stdout);
6276

63-
console.log('Container created');
77+
// The promise that docker.run returns waits for the container to *finish*. To wait until
78+
// the container has started, we wait for the first stream output:
79+
startupPromise = new Promise((resolve) => stream.on('data', resolve));
80+
} else {
81+
// When we can't attach to wait for input (e.g. PHP Apache, where window events cause
82+
// problems) we just wait briefly instead:
83+
startupPromise = delay(500);
84+
}
6485

65-
container.start();
86+
console.log('Container created');
6687

67-
// The promise that docker.run returns waits for the container to *finish*. To wait until the
68-
// container has started, we wait for the first stream output:
69-
await new Promise((resolve) => stream.on('data', resolve));
88+
await container.start();
89+
await startupPromise;
7090

7191
return container.id;
7292
}
@@ -108,7 +128,9 @@ describe('Docker single-container interceptor', function () {
108128
const { interceptor, server } = await interceptorSetup;
109129
const mainRule = await server.forGet('https://example.com').thenReply(404);
110130

111-
const containerId = await buildAndRun(target.toLowerCase(), 'https://example.com');
131+
const containerId = await buildAndRun(target.toLowerCase(), {
132+
arguments: ['https://example.com']
133+
});
112134

113135
await delay(500);
114136
expect(
@@ -117,12 +139,41 @@ describe('Docker single-container interceptor', function () {
117139

118140
await interceptor.activate(server.port, { containerId });
119141
console.log('Container intercepted');
120-
121142
await new Promise((resolve) => server.on('response', resolve));
122143

123144
const seenRequests = await mainRule.getSeenRequests();
124145
expect(seenRequests.map(r => r.url)).to.include('https://example.com/');
125146
});
126147
});
127148

149+
// PHP is a special case: we have to make a request to trigger it:
150+
it(`should intercept external PHP requests`, async function () {
151+
this.timeout(60000);
152+
const { interceptor, server } = await interceptorSetup;
153+
const mainRule = await server.forGet('https://example.com').thenReply(404);
154+
155+
const containerId = await buildAndRun('php', {
156+
attach: false,
157+
portBindings: { '80/tcp': [{ HostPort: '48080' }] }
158+
});
159+
160+
await delay(500);
161+
expect(
162+
_.map(((await interceptor.getMetadata!('summary')).targets), ({ id }: any) => id)
163+
).to.include(containerId);
164+
165+
await interceptor.activate(server.port, { containerId });
166+
console.log('Container intercepted');
167+
168+
await delay(500);
169+
fetch('http://localhost:48080/?target=https://example.com')
170+
.catch(() => {});
171+
172+
// Wait for the resulting request to example.com:
173+
await new Promise((resolve) => server.on('response', resolve));
174+
175+
const seenRequests = await mainRule.getSeenRequests();
176+
expect(seenRequests.map(r => r.url)).to.include('https://example.com/');
177+
});
178+
128179
});

0 commit comments

Comments
 (0)