#!/usr/bin/env node /** * Quick single-domain end-to-end smoke test * Usage: node test/benchmark/smoke-one.mjs */ import { execSync, spawn } from "node:child_process"; import { existsSync, rmSync, mkdirSync } from "node:fs"; import * as http from "node:http"; const TMP = "/tmp/smoke-test"; async function main() { console.log("=== Smoke Test: PetCare end-to-end ===\n"); rmSync(TMP, { recursive: true, force: true }); // 1. Generate console.log("1. Generate fullstack..."); const { composeFullstack, writeFullstack } = await import("../../scripts/fullstack-composer-agent.mjs"); const prd = { projectName: "PetCare", domain: "pet", summary: "宠物管理", features: [], pages: [{ name: "首页", route: "/home" }], personas: [], userStories: [] }; const arch = { databaseSchema: [{ table: "pets", fields: [ { name: "id", type: "TEXT", constraints: "PK" }, { name: "owner_id", type: "TEXT", constraints: "NOT NULL, FK → users.id" }, { name: "name", type: "TEXT", constraints: "NOT NULL" }, { name: "species", type: "TEXT" }, { name: "created_at", type: "TEXT" } ] }], apiDesign: [{ resource: "pets", basePath: "/api/pets", endpoints: [ { method: "GET", path: "/api/pets" }, { method: "POST", path: "/api/pets" }, { method: "GET", path: "/api/pets/:id" }, { method: "PUT", path: "/api/pets/:id" }, { method: "DELETE", path: "/api/pets/:id" } ]}] }; const result = await composeFullstack(prd, arch); writeFullstack(result, TMP); console.log(` ✅ ${result.stats.totalFiles} files`); // 2. Install console.log("2. npm install..."); execSync("npm install --no-audit --no-fund", { cwd: TMP, encoding: "utf-8", timeout: 120000, stdio: "pipe" }); // Also rm any stale db try { rmSync(TMP + "/apps/api/data", { recursive: true, force: true }); } catch {} console.log(" ✅"); // 3. Build console.log("3. tsc --noEmit..."); execSync("npx tsc --noEmit", { cwd: TMP + "/apps/api", encoding: "utf-8", timeout: 60000, stdio: "pipe" }); console.log(" ✅"); // 4. Start server console.log("4. Start server..."); const proc = spawn("npx", ["tsx", "src/index.ts"], { cwd: TMP + "/apps/api", env: { ...process.env, JWT_SECRET: "test", DATABASE_URL: ":memory:", PORT: "5678" }, stdio: "pipe", }); await waitFor("http://localhost:5678/api/health", 15000); console.log(" ✅"); // 5. Auth console.log("5. Register..."); const regResult = await postJson("http://localhost:5678/api/auth/register", { username: `u_${Date.now()}`, password: "123456" }); const token = regResult.token; const userId = regResult.user?.id; if (!token || !userId) throw new Error(`Register failed: ${JSON.stringify(regResult)}`); console.log(` ✅ user=${userId.slice(0, 12)}`); console.log("6. Login..."); const { token: token2 } = await postJson("http://localhost:5678/api/auth/login", { username: "u1", password: "123456" }); const authToken = token2 || token; console.log(` ✅`); // 6. CRUD console.log("7. Create pet..."); const { data: pet } = await postJson("http://localhost:5678/api/pets", { name: "毛球", species: "猫", ownerId: userId }, authToken); console.log(` ✅ ${pet.id.slice(0, 12)}`); console.log("8. List pets..."); const list = await getJson("http://localhost:5678/api/pets", authToken); console.log(` ✅ ${list.data.length} pet(s)`); console.log("9. Update pet..."); await putJson(`http://localhost:5678/api/pets/${pet.id}`, { species: "狗" }, authToken); console.log(" ✅"); console.log("10. Delete pet..."); const delCode = await deleteReq(`http://localhost:5678/api/pets/${pet.id}`, authToken); console.log(` ✅ HTTP ${delCode}`); proc.kill("SIGTERM"); console.log("\n🎉 ALL SMOKE TESTS PASSED"); } // --- HTTP helpers --- function waitFor(url, timeout) { return new Promise((resolve, reject) => { const start = Date.now(); function check() { http.get(url, (res) => { res.resume(); res.on("end", resolve); }).on("error", () => { if (Date.now() - start > timeout) reject(new Error("Server never started")); else setTimeout(check, 500); }); } check(); }); } function httpReq({ url, method, body, token }) { return new Promise((resolve, reject) => { const u = new URL(url); const payload = body ? JSON.stringify(body) : undefined; const headers = { "Content-Type": "application/json" }; if (payload) headers["Content-Length"] = Buffer.byteLength(payload); if (token) headers["Authorization"] = `Bearer ${token}`; const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method, headers, timeout: 10000 }, (res) => { let d = ""; res.on("data", c => d += c); res.on("end", () => resolve({ status: res.statusCode, body: d })); }); req.on("error", reject); if (payload) req.write(payload); req.end(); }); } async function postJson(url, body, token) { const { body: b } = await httpReq({ url, method: "POST", body, token }); return JSON.parse(b); } async function getJson(url, token) { const { body: b } = await httpReq({ url, method: "GET", token }); return JSON.parse(b); } async function putJson(url, body, token) { const { body: b } = await httpReq({ url, method: "PUT", body, token }); return JSON.parse(b); } async function deleteReq(url, token) { const { status } = await httpReq({ url, method: "DELETE", token }); return status; } main().catch(err => { console.error("FAIL:", err.message); process.exit(1); });