mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Basic e2e test
This commit is contained in:
parent
042fa27519
commit
0c56c3d1f9
4 changed files with 265 additions and 0 deletions
37
.github/workflows/e2e.yml
vendored
Normal file
37
.github/workflows/e2e.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
name: E2E Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Run E2E tests
|
||||||
|
run: npm run test:e2e
|
||||||
|
|
||||||
|
- name: Clean up Docker resources
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
# Clean up any remaining containers and images
|
||||||
|
docker ps -aq --filter "name=safe-chain-e2e-test" | xargs -r docker rm -f
|
||||||
|
docker images -q safe-chain-e2e-test | xargs -r docker rmi -f
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node --test --experimental-test-module-mocks **/*.spec.js",
|
"test": "node --test --experimental-test-module-mocks **/*.spec.js",
|
||||||
"test:watch": "node --test --watch --experimental-test-module-mocks **/*.spec.js",
|
"test:watch": "node --test --watch --experimental-test-module-mocks **/*.spec.js",
|
||||||
|
"test:e2e": "node --test test/e2e/**/*.e2e.spec.js",
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
||||||
32
test/e2e/Dockerfile
Normal file
32
test/e2e/Dockerfile
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Install bash and basic utilities (Alpine uses apk, not apt-get)
|
||||||
|
RUN apk add --no-cache bash curl
|
||||||
|
|
||||||
|
# Create a test user to simulate real user environment (Alpine syntax)
|
||||||
|
RUN addgroup -S testuser && adduser -S testuser -G testuser -s /bin/bash
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files first for better caching
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the application
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Switch to test user
|
||||||
|
USER testuser
|
||||||
|
|
||||||
|
# Create home directory structure that bash expects
|
||||||
|
RUN mkdir -p /home/testuser
|
||||||
|
|
||||||
|
# Set environment variables for testing
|
||||||
|
ENV HOME=/home/testuser
|
||||||
|
ENV SHELL=/bin/bash
|
||||||
|
|
||||||
|
# Default command runs our test
|
||||||
|
CMD ["bash", "test/e2e/test-setup.sh"]
|
||||||
195
test/e2e/setup.e2e.spec.js
Normal file
195
test/e2e/setup.e2e.spec.js
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
import { describe, it, before, after } from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { execSync, spawn } from "node:child_process";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const projectRoot = path.resolve(__dirname, "../..");
|
||||||
|
|
||||||
|
describe("E2E: safe-chain setup command", () => {
|
||||||
|
const imageName = "safe-chain-e2e-test";
|
||||||
|
const containerName = "safe-chain-e2e-test-container";
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
console.log("Building Docker image for e2e tests...");
|
||||||
|
try {
|
||||||
|
execSync(`docker build -t ${imageName} -f test/e2e/Dockerfile .`, {
|
||||||
|
cwd: projectRoot,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
console.log("Docker image built successfully");
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to build Docker image: ${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
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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"
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 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}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
`
|
||||||
|
]);
|
||||||
|
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue