diff --git a/tests/package/postv1-runtime.test.mjs b/tests/package/postv1-runtime.test.mjs new file mode 100644 index 0000000..d69132c --- /dev/null +++ b/tests/package/postv1-runtime.test.mjs @@ -0,0 +1,82 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { tmpdir } from "node:os"; +import { spawn } from "node:child_process"; + +const packageRoot = resolve(process.env.DADA_POSTV1_PACKAGE_ROOT ?? ".build/portable-release/Dada-P0A-0.0.0-win-x64"); +const port = 43121; + +async function waitForHealth(child) { + const deadline = Date.now() + 15_000; + while (Date.now() < deadline) { + if (child.exitCode !== null) throw new Error(`packaged api exited: ${child.exitCode}: ${child.errorOutput ?? ""}`); + try { + const response = await fetch(`http://127.0.0.1:${port}/healthz`, { headers: { host: `127.0.0.1:${port}` } }); + if (response.ok) return; + } catch {} + await new Promise((resolveDelay) => setTimeout(resolveDelay, 100)); + } + throw new Error("packaged api health timeout"); +} + +function startApi(configPath, dataRoot) { + const child = spawn(join(packageRoot, "runtime", "node.exe"), [join(packageRoot, "server", "api.mjs"), "--dada-credential-stdin"], { + cwd: packageRoot, + env: { ...process.env, DADA_INSTANCE_CONFIG_PATH: configPath, DADA_SUPPORT_GATE_ROOT: join(packageRoot, "web", "support-gate"), DADA_WEB_ROOT: join(packageRoot, "web") }, + stdio: ["pipe", "ignore", "pipe"], + windowsHide: true, + }); + child.errorOutput = ""; + child.stderr.setEncoding("utf8"); + child.stderr.on("data", (chunk) => { child.errorOutput += chunk; }); + child.stdin.end(JSON.stringify({ "Dada/P0A/api/amap": "", "Dada/P0A/api/resend": "", "Dada/P0A/admin/pepper": "portable-test-pepper-00000000000000000000000000000000" })); + return child; +} + +async function stop(child) { + if (child.exitCode === null) { + child.kill(); + await new Promise((resolveExit) => child.once("exit", resolveExit)); + } +} + +test("portable package serves the product and keeps SQLite data across API restart", async () => { + assert.ok(existsSync(join(packageRoot, "Dada.exe"))); + assert.ok(existsSync(join(packageRoot, "web", "index.html"))); + assert.match(await readFile(join(packageRoot, "server", "worker", "dist", "worker.js"), "utf8"), /GenerationProcessor/); + const root = await mkdtemp(join(tmpdir(), "dada-postv1-")); + const dataRoot = join(root, "data"); + await mkdir(join(dataRoot, "db"), { recursive: true }); + const configPath = join(root, "instance.json"); + await writeFile(configPath, JSON.stringify({ data_root: dataRoot, initialized: true, instance_id: "portable-test", schema_version: 1, secure_config_revision: 1, admin_allowlist_hashes: [], admin_recovery_hashes: [] })); + let api = startApi(configPath, dataRoot); + try { + await waitForHealth(api); + const support = await fetch(`http://127.0.0.1:${port}/api/v1/support/check`, { + method: "POST", + headers: { host: `127.0.0.1:${port}`, origin: `http://127.0.0.1:${port}`, "content-type": "application/json", "sec-ch-ua": '"Google Chrome";v="150"', "sec-ch-ua-full-version-list": '"Google Chrome";v="150.0.0.0"', "sec-ch-ua-platform": '"Windows"' }, + body: JSON.stringify({ brands: [{ brand: "Google Chrome", version: "150" }], full_version_list: [{ brand: "Google Chrome", version: "150.0.0.0" }], platform: "Windows" }), + }); + assert.ok([200, 426].includes(support.status)); + if (support.status === 200) { + const cookie = support.headers.get("set-cookie")?.split(";", 1)[0]; + const page = await fetch(`http://127.0.0.1:${port}/app`, { headers: { host: `127.0.0.1:${port}`, cookie } }); + assert.equal(page.status, 200); + assert.match(await page.text(), /
<\/div>/); + } + assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3"))); + } finally { + await stop(api); + } + api = startApi(configPath, dataRoot); + try { + await waitForHealth(api); + assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3"))); + } finally { + await stop(api); + await rm(root, { recursive: true, force: true }); + } +});