147 lines
5.5 KiB
JavaScript
147 lines
5.5 KiB
JavaScript
#!/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); });
|