Skip to content

Commit f14c510

Browse files
committed
Add e2e tests for esbuild
1 parent 5e165f7 commit f14c510

File tree

8 files changed

+506
-175
lines changed

8 files changed

+506
-175
lines changed
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
import { spawn } from "child_process";
2+
import { resolve } from "path";
3+
import { test } from "node:test";
4+
import { equal, fail, match, doesNotMatch, ok } from "node:assert";
5+
import { getRandomPort } from "./utils/get-port.mjs";
6+
import { timeout } from "./utils/timeout.mjs";
7+
import { spawnSync } from "node:child_process";
8+
import { join } from "path";
9+
10+
const pathToAppDir = resolve(
11+
import.meta.dirname,
12+
"../../sample-apps/esbuild-bundle"
13+
);
14+
const port = await getRandomPort();
15+
const port2 = await getRandomPort();
16+
const port3 = await getRandomPort();
17+
const port4 = await getRandomPort();
18+
19+
function buildApp(format, appPath) {
20+
const { stderr } = spawnSync(`node`, ["./build.mjs"], {
21+
cwd: pathToAppDir,
22+
env: {
23+
...process.env,
24+
BUNDLE_FORMAT: format,
25+
APP_PATH: appPath,
26+
},
27+
});
28+
29+
if (stderr && stderr.toString().length > 0) {
30+
throw new Error(`Failed to build: ${stderr.toString()}`);
31+
}
32+
}
33+
34+
test("it blocks request in blocking mode (CJS)", async () => {
35+
buildApp("cjs", "src/app-cjs.ts");
36+
37+
const server = spawn(`node`, ["./build/app-cjs.js", port], {
38+
cwd: pathToAppDir,
39+
env: {
40+
...process.env,
41+
AIKIDO_DEBUG: "true",
42+
AIKIDO_BLOCK: "true",
43+
},
44+
});
45+
46+
try {
47+
server.on("error", (err) => {
48+
fail(err.message);
49+
});
50+
51+
let stdout = "";
52+
server.stdout.on("data", (data) => {
53+
stdout += data.toString();
54+
});
55+
56+
let stderr = "";
57+
server.stderr.on("data", (data) => {
58+
stderr += data.toString();
59+
});
60+
61+
// Wait for the server to start
62+
await timeout(2000);
63+
64+
const [sqlInjection, normalAdd] = await Promise.all([
65+
fetch(`http://127.0.0.1:${port}/add`, {
66+
method: "POST",
67+
body: JSON.stringify({ name: "Njuska'); DELETE FROM cats_3;-- H" }),
68+
headers: {
69+
"Content-Type": "application/json",
70+
},
71+
signal: AbortSignal.timeout(5000),
72+
}),
73+
fetch(`http://127.0.0.1:${port}/add`, {
74+
method: "POST",
75+
body: JSON.stringify({ name: "Miau" }),
76+
headers: {
77+
"Content-Type": "application/json",
78+
},
79+
signal: AbortSignal.timeout(5000),
80+
}),
81+
]);
82+
83+
equal(sqlInjection.status, 500);
84+
equal(normalAdd.status, 200);
85+
match(stdout, /Starting agent/);
86+
match(stderr, /Zen has blocked an SQL injection/);
87+
match(
88+
stderr,
89+
/ The new instrumentation system with ESM support is still under active development/
90+
);
91+
doesNotMatch(stderr, /Zen has already been initialized/);
92+
} catch (err) {
93+
fail(err);
94+
} finally {
95+
server.kill();
96+
}
97+
});
98+
99+
test("it does not block request in monitoring mode (CJS)", async () => {
100+
buildApp("cjs", "src/app-cjs.ts");
101+
102+
const server = spawn(`node`, ["./build/app-cjs.js", port2], {
103+
cwd: pathToAppDir,
104+
env: {
105+
...process.env,
106+
AIKIDO_DEBUG: "true",
107+
AIKIDO_BLOCK: "false",
108+
},
109+
});
110+
111+
try {
112+
server.on("error", (err) => {
113+
fail(err.message);
114+
});
115+
116+
let stdout = "";
117+
server.stdout.on("data", (data) => {
118+
stdout += data.toString();
119+
});
120+
121+
let stderr = "";
122+
server.stderr.on("data", (data) => {
123+
stderr += data.toString();
124+
});
125+
126+
// Wait for the server to start
127+
await timeout(2000);
128+
129+
const [sqlInjection, normalAdd] = await Promise.all([
130+
fetch(`http://127.0.0.1:${port2}/add`, {
131+
method: "POST",
132+
body: JSON.stringify({ name: "Njuska'); DELETE FROM cats_3;-- H" }),
133+
headers: {
134+
"Content-Type": "application/json",
135+
},
136+
signal: AbortSignal.timeout(5000),
137+
}),
138+
fetch(`http://127.0.0.1:${port2}/add`, {
139+
method: "POST",
140+
body: JSON.stringify({ name: "Miau" }),
141+
headers: {
142+
"Content-Type": "application/json",
143+
},
144+
signal: AbortSignal.timeout(5000),
145+
}),
146+
]);
147+
148+
equal(sqlInjection.status, 200);
149+
equal(normalAdd.status, 200);
150+
match(stdout, /Starting agent/);
151+
doesNotMatch(stderr, /Zen has blocked an SQL injection/);
152+
match(
153+
stderr,
154+
/ The new instrumentation system with ESM support is still under active development/
155+
);
156+
doesNotMatch(stderr, /Zen has already been initialized/);
157+
} catch (err) {
158+
fail(err);
159+
} finally {
160+
server.kill();
161+
}
162+
});
163+
164+
test("it blocks request in blocking mode (ESM)", async () => {
165+
buildApp("esm", "src/app-esm.ts");
166+
167+
const server = spawn(
168+
`node`,
169+
["-r", "@aikidosec/firewall/instrument", "./app-esm.js", port3],
170+
{
171+
cwd: join(pathToAppDir, "build"),
172+
env: {
173+
...process.env,
174+
AIKIDO_DEBUG: "true",
175+
AIKIDO_BLOCK: "true",
176+
},
177+
}
178+
);
179+
180+
try {
181+
server.on("error", (err) => {
182+
fail(err.message);
183+
});
184+
185+
let stdout = "";
186+
server.stdout.on("data", (data) => {
187+
stdout += data.toString();
188+
});
189+
190+
let stderr = "";
191+
server.stderr.on("data", (data) => {
192+
stderr += data.toString();
193+
});
194+
195+
// Wait for the server to start
196+
await timeout(2000);
197+
198+
const [sqlInjection, normalAdd] = await Promise.all([
199+
fetch(`http://127.0.0.1:${port3}/add`, {
200+
method: "POST",
201+
body: JSON.stringify({ name: "Njuska'); DELETE FROM cats_3;-- H" }),
202+
headers: {
203+
"Content-Type": "application/json",
204+
},
205+
signal: AbortSignal.timeout(5000),
206+
}),
207+
fetch(`http://127.0.0.1:${port3}/add`, {
208+
method: "POST",
209+
body: JSON.stringify({ name: "Miau" }),
210+
headers: {
211+
"Content-Type": "application/json",
212+
},
213+
signal: AbortSignal.timeout(5000),
214+
}),
215+
]);
216+
217+
equal(sqlInjection.status, 500);
218+
equal(normalAdd.status, 200);
219+
match(stdout, /Starting agent/);
220+
match(stderr, /Zen has blocked an SQL injection/);
221+
match(
222+
stderr,
223+
/ The new instrumentation system with ESM support is still under active development/
224+
);
225+
doesNotMatch(stderr, /Zen has already been initialized/);
226+
doesNotMatch(
227+
stderr,
228+
/Your application seems to be running in ESM mode. You need to use the new hook system to enable Zen. See our ESM documentation for setup instructions./
229+
);
230+
} catch (err) {
231+
fail(err);
232+
} finally {
233+
server.kill();
234+
}
235+
});
236+
237+
test("it does not block request in monitoring mode (ESM)", async () => {
238+
buildApp("esm", "src/app-esm.ts");
239+
240+
const server = spawn(
241+
`node`,
242+
["-r", "@aikidosec/firewall/instrument", "./app-esm.js", port4],
243+
{
244+
cwd: join(pathToAppDir, "build"),
245+
env: {
246+
...process.env,
247+
AIKIDO_DEBUG: "true",
248+
AIKIDO_BLOCK: "false",
249+
},
250+
}
251+
);
252+
253+
try {
254+
server.on("error", (err) => {
255+
fail(err.message);
256+
});
257+
258+
let stdout = "";
259+
server.stdout.on("data", (data) => {
260+
stdout += data.toString();
261+
});
262+
263+
let stderr = "";
264+
server.stderr.on("data", (data) => {
265+
stderr += data.toString();
266+
});
267+
268+
// Wait for the server to start
269+
await timeout(2000);
270+
271+
const [sqlInjection, normalAdd] = await Promise.all([
272+
fetch(`http://127.0.0.1:${port4}/add`, {
273+
method: "POST",
274+
body: JSON.stringify({ name: "Njuska'); DELETE FROM cats_3;-- H" }),
275+
headers: {
276+
"Content-Type": "application/json",
277+
},
278+
signal: AbortSignal.timeout(5000),
279+
}),
280+
fetch(`http://127.0.0.1:${port4}/add`, {
281+
method: "POST",
282+
body: JSON.stringify({ name: "Miau" }),
283+
headers: {
284+
"Content-Type": "application/json",
285+
},
286+
signal: AbortSignal.timeout(5000),
287+
}),
288+
]);
289+
290+
equal(sqlInjection.status, 200);
291+
equal(normalAdd.status, 200);
292+
match(stdout, /Starting agent/);
293+
doesNotMatch(stderr, /Zen has blocked an SQL injection/);
294+
match(
295+
stderr,
296+
/ The new instrumentation system with ESM support is still under active development/
297+
);
298+
doesNotMatch(stderr, /Zen has already been initialized/);
299+
doesNotMatch(
300+
stderr,
301+
/Your application seems to be running in ESM mode. You need to use the new hook system to enable Zen. See our ESM documentation for setup instructions./
302+
);
303+
} catch (err) {
304+
fail(err);
305+
} finally {
306+
server.kill();
307+
}
308+
});
309+
310+
test("it throws error when Zen is initialized wrong", async () => {
311+
try {
312+
buildApp("esm", "src/app-cjs.ts");
313+
fail("Build should have failed but didn't");
314+
} catch (err) {
315+
ok(
316+
err.message.includes(
317+
"Aikido: Detected import of '@aikidosec/firewall/instrument' in your code while building an ESM bundle. Please remove this import and preload the library by running Node.js with the --require option instead. See our ESM documentation for more information."
318+
)
319+
);
320+
}
321+
322+
try {
323+
buildApp("cjs", "src/app-esm.ts");
324+
fail("Build should have failed but didn't");
325+
} catch (err) {
326+
ok(
327+
err.message.includes(
328+
"Aikido: Missing import of '@aikidosec/firewall/instrument' in your code while building a CJS bundle."
329+
)
330+
);
331+
}
332+
});

library/bundler/internal/unplugin.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ import { copyFileSync, cpSync, existsSync, mkdirSync } from "node:fs";
66
import { dirname, join } from "node:path";
77

88
type UserOptions = {
9-
execlude?: string | string[];
10-
inlineWebAssembly?: boolean;
9+
/**
10+
* Whether to copy Zen to the output directory for ESM builds.
11+
*/
12+
copyModule: boolean;
1113
};
1214

1315
let outputFormat: "cjs" | "esm" | undefined = undefined;
1416
let importFound = false;
17+
let userOptions: UserOptions | undefined = undefined;
1518

1619
export const basePlugin: UnpluginInstance<UserOptions | undefined, false> =
17-
createUnplugin(() => {
20+
createUnplugin((options) => {
21+
userOptions = options;
22+
1823
return {
1924
name: "zen-js-bundler-plugin",
2025

@@ -147,7 +152,7 @@ function copyFiles(outDir: string, format: "cjs" | "esm") {
147152
]) {
148153
copyFileSync(join(zenLibDir, file), join(outDir, file));
149154
}
150-
} else if (format === "esm") {
155+
} else if (format === "esm" && userOptions?.copyModule !== false) {
151156
cpSync(zenLibDir, join(outDir, "node_modules", "@aikidosec", "firewall"), {
152157
recursive: true,
153158
});

0 commit comments

Comments
 (0)