140 lines
5.6 KiB
JavaScript
140 lines
5.6 KiB
JavaScript
"use strict";
|
||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
const child_process_1 = require("child_process");
|
||
const http_1 = __importDefault(require("http"));
|
||
// We will start the runner server on port 3334
|
||
const PORT = 3334;
|
||
const BASE_URL = `http://localhost:${PORT}`;
|
||
console.log("🧪 Starting AgentRunner Hardening Test Suite...");
|
||
// Set up environment variables
|
||
const env = {
|
||
...process.env,
|
||
PORT: String(PORT),
|
||
AGENT_RUNNER_SECRET: "test-secret-123",
|
||
GOOGLE_API_KEY: "dummy-key-for-testing", // Pass dummy key to avoid Gemini API initialization crash
|
||
VIBN_API_URL: "http://localhost:3335", // Mock backend
|
||
};
|
||
// Start mock backend on port 3335 to catch PATCH callbacks and verify headers
|
||
let receivedHeaders = null;
|
||
let receivedBody = null;
|
||
const mockBackend = http_1.default.createServer((req, res) => {
|
||
receivedHeaders = req.headers;
|
||
let body = "";
|
||
req.on("data", (chunk) => {
|
||
body += chunk;
|
||
});
|
||
req.on("end", () => {
|
||
try {
|
||
receivedBody = JSON.parse(body);
|
||
}
|
||
catch {
|
||
receivedBody = body;
|
||
}
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
res.end(JSON.stringify({ ok: true }));
|
||
});
|
||
});
|
||
mockBackend.listen(3335, () => {
|
||
console.log("✓ Mock backend server listening on port 3335");
|
||
});
|
||
// Spawn the runner server
|
||
const serverProcess = (0, child_process_1.spawn)("npx", ["ts-node", "src/server.ts"], {
|
||
env,
|
||
stdio: "pipe",
|
||
});
|
||
// Wait for server to start
|
||
serverProcess.stdout.on("data", (data) => {
|
||
const output = data.toString();
|
||
console.log(`[Server Out] ${output.trim()}`);
|
||
});
|
||
serverProcess.stderr.on("data", (data) => {
|
||
console.error(`[Server Err] ${data.toString()}`);
|
||
});
|
||
// Helper function to sleep
|
||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||
async function runTests() {
|
||
// Wait 4 seconds for server to boot
|
||
await sleep(4000);
|
||
let passed = 0;
|
||
let failed = 0;
|
||
const assert = (condition, message) => {
|
||
if (condition) {
|
||
console.log(` 🟢 PASSED: ${message}`);
|
||
passed++;
|
||
}
|
||
else {
|
||
console.error(` 🔴 FAILED: ${message}`);
|
||
failed++;
|
||
}
|
||
};
|
||
try {
|
||
// Test 1: Empty appPath should be accepted and fall back to "."
|
||
console.log("\n1️⃣ Testing appPath empty string fallback...");
|
||
const res1 = await fetch(`${BASE_URL}/agent/execute`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
sessionId: "test-session-1",
|
||
projectId: "test-project-1",
|
||
task: "Test empty appPath",
|
||
appPath: "", // Empty string!
|
||
giteaRepo: "test-repo",
|
||
}),
|
||
});
|
||
assert(res1.status === 202, `Should return 202, got ${res1.status}`);
|
||
const data1 = (await res1.json());
|
||
assert(data1.sessionId === "test-session-1", `Should return correct sessionId, got ${data1.sessionId}`);
|
||
// Test 2: Missing sessionId should return 400
|
||
console.log("\n2️⃣ Testing missing required parameters (sessionId)...");
|
||
const res2 = await fetch(`${BASE_URL}/agent/execute`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
projectId: "test-project-1",
|
||
task: "Test missing sessionId",
|
||
appPath: ".",
|
||
}),
|
||
});
|
||
assert(res2.status === 400, `Should return 400, got ${res2.status}`);
|
||
// Test 3: Emergency callback headers should include x-agent-runner-secret
|
||
console.log("\n3️⃣ Testing early failure callback headers...");
|
||
// Trigger a clone failure by passing a malformed giteaRepo containing slash,
|
||
// which triggers clone instead of default workspace but will fail clone.
|
||
console.log("Triggering clone failure on mock Gitea...");
|
||
const res3 = await fetch(`${BASE_URL}/agent/execute`, {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
sessionId: "test-session-3",
|
||
projectId: "test-project-3",
|
||
task: "Trigger crash",
|
||
appPath: ".",
|
||
giteaRepo: "invalid_owner/invalid_repo",
|
||
}),
|
||
});
|
||
assert(res3.status === 202, `Should return 202 Accepted, got ${res3.status}`);
|
||
// Wait for server to process async task and fail, calling our mock backend PATCH
|
||
console.log("Waiting for runner callback on mock backend...");
|
||
await sleep(4000);
|
||
assert(receivedHeaders !== null, "Should call mock backend PATCH endpoint");
|
||
if (receivedHeaders) {
|
||
assert(receivedHeaders["x-agent-runner-secret"] === "test-secret-123", `Callback should include secret header 'test-secret-123', got '${receivedHeaders["x-agent-runner-secret"]}'`);
|
||
assert(receivedBody && receivedBody.status === "failed", `Callback body should have status 'failed', got '${receivedBody?.status}'`);
|
||
}
|
||
}
|
||
catch (err) {
|
||
console.error("Test execution failed with exception:", err);
|
||
}
|
||
finally {
|
||
console.log("\n🧹 Cleaning up test servers...");
|
||
serverProcess.kill();
|
||
mockBackend.close();
|
||
console.log(`\n📊 Tests complete. Passed: ${passed}, Failed: ${failed}`);
|
||
process.exit(failed > 0 ? 1 : 0);
|
||
}
|
||
}
|
||
runTests();
|