From 82b1e79f637f6541e520394ce2218b94c8aeeb3e Mon Sep 17 00:00:00 2001 From: samirasidd Date: Tue, 20 Oct 2026 16:30:57 +0600 Subject: [PATCH 1/3] ci: run existing unit tests --- package.json | 3 ++- scripts/run-tests.mjs | 43 +++++++++++++++++++++++++++++++++++++++++++ server/lib/ip.test.ts | 1 + 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 scripts/run-tests.mjs diff --git a/package.json b/package.json index 4ebb8b298..b5cf45f2d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "email": "email dev --dir server/emails/templates --port 3005", "build:cli": "node esbuild.mjs -e cli/index.ts -o dist/cli.mjs", "format:check": "prettier --check .", - "format": "prettier --write ." + "format": "prettier --write .", + "test": "node scripts/run-tests.mjs" }, "dependencies": { "@asteasolutions/zod-to-openapi": "9.1.0", diff --git a/scripts/run-tests.mjs b/scripts/run-tests.mjs new file mode 100644 index 000000000..f9c21f1cf --- /dev/null +++ b/scripts/run-tests.mjs @@ -0,0 +1,43 @@ +import { spawnSync } from "node:child_process"; +import { readdirSync, statSync } from "node:fs"; +import { join } from "node:path"; + +// Test files that fail today. Give each a reason, fix it, then remove it. +const knownFailing = new Map([ + [ + "server/setup/migrationsSqlite.backup.test.ts", + "migration 1.24.0 fails: missing resources table in seeded db" + ], + [ + "server/lib/traefik/traefikConfig.test.ts", + "config undefined on import (logger circular dependency)" + ] +]); +const skipDirs = new Set(["node_modules", ".next", "dist", ".git"]); + +function find(dir) { + return readdirSync(dir).flatMap((name) => { + if (skipDirs.has(name) || name.startsWith("private")) return []; + const p = join(dir, name); + if (statSync(p).isDirectory()) return find(p); + return p.endsWith(".test.ts") ? [p] : []; + }); +} + +const files = find("."); +const failed = []; +for (const f of files) { + if (knownFailing.has(f)) { + console.log(`\n== SKIPPED ${f}: ${knownFailing.get(f)}`); + continue; + } + console.log(`\n== ${f}`); + const r = spawnSync("npx", ["tsx", f], { stdio: "inherit", shell: true }); + if (r.status !== 0) failed.push(f); +} +const ran = files.length - knownFailing.size; +console.log(`\n${ran - failed.length}/${ran} test files passed`); +if (failed.length) { + console.log("FAILED:\n" + failed.join("\n")); + process.exit(1); +} diff --git a/server/lib/ip.test.ts b/server/lib/ip.test.ts index 70436e05a..cf0f45ff6 100644 --- a/server/lib/ip.test.ts +++ b/server/lib/ip.test.ts @@ -1,3 +1,4 @@ +import "../extendZod"; import { cidrToRange, findNextAvailableCidr } from "./ip"; import { assertEquals } from "@test/assert"; From d225930f21c02a488dabd3def0128635f4dda7f2 Mon Sep 17 00:00:00 2001 From: samirasidd Date: Wed, 21 Oct 2026 01:26:15 +0600 Subject: [PATCH 2/3] ci: add unit test step to workflow --- .github/workflows/test.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bcfe95ee0..09fe0e398 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,12 +19,6 @@ jobs: - name: Install Node uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: - # Pin to the version the app actually ships on (Dockerfile / - # Dockerfile.dev both use 24.18.1; .nvmrc pins the 24 major). - # A bare '24' floats to the latest 24.x; Node >= 24.19.0 added - # node::ObjectWrap cleanup hooks that crash better-sqlite3 on teardown - # ("Assertion failed: (env) != nullptr" in RemoveEnvironmentCleanupHook), - # which intermittently kills `npm run dev` in the step below. node-version: '24.18.1' - name: Copy config file @@ -48,6 +42,9 @@ jobs: - name: Test with tsc run: npx tsc --noEmit + - name: Run unit tests + run: npm test + - name: Start app in background run: | nohup npm run dev > app.log 2>&1 & @@ -63,9 +60,6 @@ jobs: echo "App is up" exit 0 fi - # Fail fast (and show why) if the process is gone. Read the PID - # safely: if it's missing/empty (e.g. nohup never started), treat - # that as "not running" instead of passing a bad arg to kill. pid="$(cat app.pid 2>/dev/null || true)" if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then echo "App process exited before becoming available. Output:" From 4c63ea0d7645cb4805eed10192ad3c8ceeb0151e Mon Sep 17 00:00:00 2001 From: samirasidd Date: Thu, 22 Oct 2026 12:16:18 +0600 Subject: [PATCH 3/3] ci: restore workflow comments, keep only the new step --- .github/workflows/test.yml | 9 +++++++++ scripts/run-tests.mjs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09fe0e398..efa373b50 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,12 @@ jobs: - name: Install Node uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 with: + # Pin to the version the app actually ships on (Dockerfile / + # Dockerfile.dev both use 24.18.1; .nvmrc pins the 24 major). + # A bare '24' floats to the latest 24.x; Node >= 24.19.0 added + # node::ObjectWrap cleanup hooks that crash better-sqlite3 on teardown + # ("Assertion failed: (env) != nullptr" in RemoveEnvironmentCleanupHook), + # which intermittently kills `npm run dev` in the step below. node-version: '24.18.1' - name: Copy config file @@ -60,6 +66,9 @@ jobs: echo "App is up" exit 0 fi + # Fail fast (and show why) if the process is gone. Read the PID + # safely: if it's missing/empty (e.g. nohup never started), treat + # that as "not running" instead of passing a bad arg to kill. pid="$(cat app.pid 2>/dev/null || true)" if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then echo "App process exited before becoming available. Output:" diff --git a/scripts/run-tests.mjs b/scripts/run-tests.mjs index f9c21f1cf..3fd867aeb 100644 --- a/scripts/run-tests.mjs +++ b/scripts/run-tests.mjs @@ -32,7 +32,7 @@ for (const f of files) { continue; } console.log(`\n== ${f}`); - const r = spawnSync("npx", ["tsx", f], { stdio: "inherit", shell: true }); + const r = spawnSync("npx", ["tsx", f], { stdio: "inherit", shell: process.platform === "win32" }); if (r.status !== 0) failed.push(f); } const ran = files.length - knownFailing.size;