Merge pull request #6 from MoYiC6/fix/cross-platform-test-compatibility
fix: harden test suite for Windows/CRLF cross-platform compatibility
This commit is contained in:
6 files changed
+98
-12
No files matched your search
+1
-1
@@ -8,7 +8,7 @@
|
||||
"dev": "node --import tsx src/index.ts",
|
||||
"dev:continuous": "MONITOR_CONTINUOUS=true node --import tsx src/index.ts",
|
||||
"dev:ui": "UI_MODE=true node --import tsx src/index.ts",
|
||||
"smoke:ui": "bash scripts/ui-smoke.sh",
|
||||
"smoke:ui": "node scripts/ui-smoke.js",
|
||||
"command:backup-export": "APP_COMMAND=backup-export node --import tsx src/index.ts",
|
||||
"avatars:export": "node --import tsx scripts/export-staff-avatars.ts",
|
||||
"command:import-validate": "APP_COMMAND=import-validate node --import tsx src/index.ts",
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Cross-platform UI smoke test.
|
||||
* Replaces scripts/ui-smoke.sh for Windows compatibility.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
const { spawn } = require("node:child_process");
|
||||
const http = require("node:http");
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs");
|
||||
|
||||
const PORT = process.env.UI_SMOKE_PORT || "4516";
|
||||
const WAIT_SECONDS = Number(process.env.UI_SMOKE_WAIT_SECONDS || "10");
|
||||
const ROOT = path.resolve(__dirname, "..");
|
||||
const LOG_DIR = path.join(ROOT, "runtime");
|
||||
const LOG_FILE = path.join(LOG_DIR, `ui-smoke-${PORT}.log`);
|
||||
|
||||
fs.mkdirSync(LOG_DIR, { recursive: true });
|
||||
|
||||
const logStream = fs.createWriteStream(LOG_FILE);
|
||||
const child = spawn(process.execPath, ["--import", "tsx", "src/index.ts"], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, UI_MODE: "true", UI_PORT: PORT },
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
child.stdout.pipe(logStream);
|
||||
child.stderr.pipe(logStream);
|
||||
|
||||
function cleanup(code) {
|
||||
try { child.kill(); } catch (_) { /* ignore */ }
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
process.on("SIGINT", () => cleanup(1));
|
||||
process.on("SIGTERM", () => cleanup(1));
|
||||
|
||||
function fetch(urlPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = http.get(`http://127.0.0.1:${PORT}${urlPath}`, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (c) => { data += c; });
|
||||
res.on("end", () => resolve(data));
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.setTimeout(3000, () => { req.destroy(); reject(new Error("timeout")); });
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForUI() {
|
||||
const deadline = Date.now() + WAIT_SECONDS * 1000;
|
||||
while (Date.now() < deadline) {
|
||||
try {
|
||||
await fetch("/");
|
||||
return;
|
||||
} catch (_) {
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
}
|
||||
console.error(`UI smoke failed: server did not become ready within ${WAIT_SECONDS}s.`);
|
||||
try { console.error(fs.readFileSync(LOG_FILE, "utf8")); } catch (_) { /* ignore */ }
|
||||
cleanup(1);
|
||||
}
|
||||
|
||||
async function checkPage(urlPath, keywords, label) {
|
||||
const body = await fetch(urlPath);
|
||||
const found = keywords.some((kw) => body.includes(kw));
|
||||
if (!found) {
|
||||
console.error(`FAIL: ${label} — none of [${keywords.join(", ")}] found in response (${body.length} bytes).`);
|
||||
cleanup(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await waitForUI();
|
||||
await checkPage("/", ["OpenClaw", "Control Center", "usage", "lang="], "GET /");
|
||||
await checkPage("/docs?lang=en", ["Open document workbench", "Control Center", "Docs"], "GET /docs?lang=en");
|
||||
console.log(`UI smoke passed on http://127.0.0.1:${PORT}`);
|
||||
cleanup(0);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("UI smoke error:", err);
|
||||
cleanup(1);
|
||||
});
|
||||
@@ -11,13 +11,11 @@ const PUBLIC_FILES = [
|
||||
"README.md",
|
||||
"docs/ARCHITECTURE.md",
|
||||
"docs/PROGRESS.md",
|
||||
"docs/mission.runbook.md",
|
||||
"docs/mission-control-runbook-v2.md",
|
||||
"ecosystem.config.cjs",
|
||||
"mission.config.json",
|
||||
"scripts/run_verifier.sh",
|
||||
"scripts/mc_dod_evaluator.py",
|
||||
"scripts/mc_rollback_plan.py",
|
||||
"workflows/pandas_v3_autopilot.lobster",
|
||||
];
|
||||
|
||||
test("repo includes baseline open-source release metadata", () => {
|
||||
@@ -25,9 +23,9 @@ test("repo includes baseline open-source release metadata", () => {
|
||||
assert(existsSync(path.join(ROOT, "LICENSE")), "Expected LICENSE to exist.");
|
||||
|
||||
const ignore = readFileSync(path.join(ROOT, ".gitignore"), "utf8");
|
||||
assert.match(ignore, /(^|\n)node_modules\/(\n|$)/);
|
||||
assert.match(ignore, /(^|\n)dist\/(\n|$)/);
|
||||
assert.match(ignore, /(^|\n)\/?runtime\/(\n|$)/);
|
||||
assert.match(ignore, /(^|\r?\n)node_modules\/(\r?\n|$)/);
|
||||
assert.match(ignore, /(^|\r?\n)dist\/(\r?\n|$)/);
|
||||
assert.match(ignore, /(^|\r?\n)\/?runtime\/(\r?\n|$)/);
|
||||
|
||||
const license = readFileSync(path.join(ROOT, "LICENSE"), "utf8");
|
||||
assert.match(license, /MIT License/);
|
||||
|
||||
@@ -121,10 +121,11 @@ test("done-checklist route logic builds readiness payload", async () => {
|
||||
});
|
||||
|
||||
test("backup export command path writes bundle and operation audit entry", async () => {
|
||||
const run = await runCommand("npm", ["run", "command:backup-export"], {
|
||||
const run = await runCommand(process.execPath, ["--import", "tsx", "src/index.ts"], {
|
||||
cwd: PROJECT_ROOT,
|
||||
env: {
|
||||
...process.env,
|
||||
APP_COMMAND: "backup-export",
|
||||
READONLY_MODE: "true",
|
||||
APPROVAL_ACTIONS_ENABLED: "false",
|
||||
APPROVAL_ACTIONS_DRY_RUN: "true",
|
||||
@@ -148,7 +149,7 @@ test("backup export command path writes bundle and operation audit entry", async
|
||||
const metadata = asRecord(commandEntry.metadata);
|
||||
assert.equal(typeof metadata?.path, "string");
|
||||
const writtenPath = metadata?.path as string;
|
||||
assert(writtenPath.includes("/runtime/exports/"));
|
||||
assert(writtenPath.replace(/\\/g, "/").includes("/runtime/exports/"));
|
||||
assert(await fileExists(writtenPath), "Expected backup export file path from audit metadata to exist.");
|
||||
});
|
||||
|
||||
@@ -182,10 +183,11 @@ test("acks-prune command removes expired records and writes audit entry", async
|
||||
await writeFile(ACKS_PATH, `${JSON.stringify(fixture, null, 2)}\n`, "utf8");
|
||||
|
||||
try {
|
||||
const run = await runCommand("npm", ["run", "command:acks-prune"], {
|
||||
const run = await runCommand(process.execPath, ["--import", "tsx", "src/index.ts"], {
|
||||
cwd: PROJECT_ROOT,
|
||||
env: {
|
||||
...process.env,
|
||||
APP_COMMAND: "acks-prune",
|
||||
READONLY_MODE: "true",
|
||||
APPROVAL_ACTIONS_ENABLED: "false",
|
||||
APPROVAL_ACTIONS_DRY_RUN: "true",
|
||||
|
||||
@@ -388,7 +388,7 @@ test("usage dashboard includes token type share and cron token share sections",
|
||||
});
|
||||
|
||||
test("memory and workspace sections expose editable file workbenches", async () => {
|
||||
const source = await readFile("src/ui/server.ts", "utf8");
|
||||
const source = (await readFile("src/ui/server.ts", "utf8")).replace(/\r\n/g, "\n");
|
||||
assert(source.includes("/api/files"));
|
||||
assert(source.includes("/api/files/content"));
|
||||
assert(source.includes("scope must be one of: memory, workspace"));
|
||||
|
||||
@@ -181,7 +181,7 @@ test("usage-cost snapshot backfills subscription consumed from runtime usage whe
|
||||
assert.equal(usage.subscription.limit, undefined);
|
||||
assert(usage.subscription.detail.includes("Provider remaining/limit cannot be derived"));
|
||||
assert.equal(usage.subscription.reasonCode, "runtime_backfill_only");
|
||||
assert(usage.subscription.sourcePath?.includes("sessions/*.jsonl"));
|
||||
assert(usage.subscription.sourcePath?.replace(/\\/g, "/").includes("sessions/*.jsonl"));
|
||||
assert.equal(usage.connectors.subscriptionUsage, "not_connected");
|
||||
assert(
|
||||
usage.connectors.todos.some(
|
||||
|
||||
Reference in new issue
Block a user