First setup and teardown tests

This commit is contained in:
Sander Declerck 2025-07-30 16:10:46 +02:00
parent 73b209a5f6
commit 05ebb3f19e
No known key found for this signature in database
6 changed files with 292 additions and 178 deletions

View file

@ -1,8 +1,9 @@
import { describe, it, before, after } from "node:test";
import assert from "node:assert";
import { execSync, spawn } from "node:child_process";
import { describe, it, before, beforeEach, afterEach } from "node:test";
import { execSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DockerTestContainer } from "./DockerTestContainer.js";
import assert from "node:assert";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -11,185 +12,66 @@ const projectRoot = path.resolve(__dirname, "../..");
describe("E2E: safe-chain setup command", () => {
const imageName = "safe-chain-e2e-test";
const containerName = "safe-chain-e2e-test-container";
let container;
before(async () => {
console.log("Building Docker image for e2e tests...");
// Build the Docker image for the test environment
try {
execSync(`docker build -t ${imageName} -f test/e2e/Dockerfile .`, {
cwd: projectRoot,
stdio: "inherit",
stdio: "ignore",
});
console.log("Docker image built successfully");
} catch (error) {
throw new Error(`Failed to build Docker image: ${error.message}`);
throw new Error(`Failed to setup test environment: ${error.message}`);
}
});
after(async () => {
// Clean up: remove container and image
try {
execSync(`docker rm -f ${containerName}`, { stdio: "ignore" });
} catch {
// Container might not exist, ignore
}
try {
execSync(`docker rmi ${imageName}`, { stdio: "ignore" });
} catch {
// Image might be in use, ignore
beforeEach(async () => {
// Run a new Docker container for each test
container = new DockerTestContainer(imageName, containerName);
await container.start();
});
afterEach(async () => {
// Stop and clean up the container after each test
if (container) {
await container.stop();
container = null;
}
});
it("should successfully run safe-chain setup and create aliases", async () => {
// Run the container and capture output
const result = await runDockerTest([
"node", "bin/safe-chain.js", "setup"
]);
for (let shell of ["bash", "zsh"]) {
it(`safe-chain setup wraps npm command after installation for ${shell}`, async () => {
// setting up the container
const installationShell = await container.openShell(shell);
await installationShell.runCommand("safe-chain setup");
// Verify setup completed successfully
assert.ok(
result.stdout.includes("Setup successful"),
"Setup should report success"
);
assert.strictEqual(
result.exitCode,
0,
`Setup should exit with code 0, got ${result.exitCode}`
);
});
const projectShell = await container.openShell(shell);
await projectShell.runCommand("cd /testapp");
const result = await projectShell.runCommand("npm i axios");
it("should create correct aliases in .bashrc", async () => {
// Run setup and then check .bashrc contents
const result = await runDockerTest([
"bash", "-c", `
node bin/safe-chain.js setup &&
echo "=== BASHRC CONTENTS ===" &&
cat /home/testuser/.bashrc
`
]);
assert.strictEqual(result.exitCode, 0, "Commands should succeed");
const bashrcContent = result.stdout;
// Check for all expected aliases
const expectedAliases = [
'alias npm="aikido-npm" # Safe-chain alias for npm',
'alias npx="aikido-npx" # Safe-chain alias for npx',
'alias yarn="aikido-yarn" # Safe-chain alias for yarn',
'alias pnpm="aikido-pnpm" # Safe-chain alias for pnpm',
'alias pnpx="aikido-pnpx" # Safe-chain alias for pnpx'
];
for (const expectedAlias of expectedAliases) {
assert.ok(
bashrcContent.includes(expectedAlias),
`Should contain alias: ${expectedAlias}`
result.output.includes("Scanning for malicious packages..."),
"Expected npm command to be wrapped by safe-chain"
);
}
});
});
it("should be idempotent (not create duplicate aliases)", async () => {
// Run setup twice and check for duplicates
const result = await runDockerTest([
"bash", "-c", `
node bin/safe-chain.js setup &&
node bin/safe-chain.js setup &&
echo "=== ALIAS COUNT ===" &&
grep -c 'alias npm="aikido-npm"' /home/testuser/.bashrc || echo 0
`
]);
it(`safe-chain teardown unwraps npm command after uninstallation for ${shell}`, async () => {
// setting up the container
const installationShell = await container.openShell(shell);
await installationShell.runCommand("safe-chain setup");
await installationShell.runCommand("safe-chain teardown");
assert.strictEqual(result.exitCode, 0, "Commands should succeed");
// Extract the count from output
const lines = result.stdout.split('\n');
const countLine = lines.find(line => line.match(/^\d+$/));
const aliasCount = parseInt(countLine || '0');
assert.strictEqual(
aliasCount,
1,
`Should have exactly 1 npm alias, found ${aliasCount}`
);
});
const projectShell = await container.openShell(shell);
await projectShell.runCommand("cd /testapp");
await projectShell.runCommand("npm i axios");
const result = await projectShell.runCommand("npm i axios");
it("should work with fresh .bashrc file", async () => {
// Ensure no .bashrc exists initially
const result = await runDockerTest([
"bash", "-c", `
rm -f /home/testuser/.bashrc &&
node bin/safe-chain.js setup &&
test -f /home/testuser/.bashrc && echo "BASHRC_CREATED" ||
echo "BASHRC_NOT_CREATED"
`
]);
assert.strictEqual(result.exitCode, 0, "Commands should succeed");
assert.ok(
result.stdout.includes("BASHRC_CREATED"),
".bashrc should be created if it doesn't exist"
);
});
it("should detect bash shell correctly", async () => {
const result = await runDockerTest([
"node", "bin/safe-chain.js", "setup"
]);
assert.strictEqual(result.exitCode, 0, "Setup should succeed");
assert.ok(
result.stdout.includes("Detected") && result.stdout.includes("Bash"),
"Should detect Bash shell"
);
});
/**
* Helper function to run a command in Docker container and return result
*/
async function runDockerTest(command) {
return new Promise((resolve, reject) => {
const dockerArgs = [
"run", "--rm",
"--name", containerName,
imageName,
...command
];
const child = spawn("docker", dockerArgs, {
cwd: projectRoot,
stdio: ["pipe", "pipe", "pipe"]
});
let stdout = "";
let stderr = "";
child.stdout.on("data", (data) => {
stdout += data.toString();
});
child.stderr.on("data", (data) => {
stderr += data.toString();
});
child.on("close", (code) => {
resolve({
exitCode: code,
stdout,
stderr
});
});
child.on("error", (error) => {
reject(new Error(`Docker command failed: ${error.message}`));
});
// Set timeout to prevent hanging tests
setTimeout(() => {
child.kill();
reject(new Error("Test timed out after 60 seconds"));
}, 60000);
assert.ok(
!result.output.includes("Scanning for malicious packages..."),
"Expected npm command to not be wrapped by safe-chain after teardown"
);
});
}
});
});