106 lines
3.3 KiB
JavaScript
106 lines
3.3 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const express = require("express");
|
|
const { openDatabase } = require("../src/database");
|
|
const { runMigrations } = require("../src/database/migrate");
|
|
const {
|
|
createDetailedHealthRouter,
|
|
createHealthRouter,
|
|
} = require("../src/health/routes");
|
|
|
|
function config() {
|
|
return {
|
|
environment: "test",
|
|
host: "127.0.0.1",
|
|
port: 0,
|
|
timeZone: "America/Chicago",
|
|
trustProxy: true,
|
|
database: { path: ":memory:" },
|
|
storage: {
|
|
endpoint: "http://private-storage:8333",
|
|
region: "us-east-1",
|
|
bucket: "announcements-v2",
|
|
accessKeyId: "must-not-leak",
|
|
secretAccessKey: "must-not-leak-either",
|
|
forcePathStyle: true,
|
|
},
|
|
processing: {
|
|
tempDirectory: "/tmp/announcements-uploads",
|
|
maxFileBytes: 1024,
|
|
maxFiles: 2,
|
|
maxInputPixels: 1000,
|
|
},
|
|
mqtt: { url: null },
|
|
notifications: { homeAssistantWebhookUrl: null },
|
|
};
|
|
}
|
|
|
|
async function withServer(storage, callback) {
|
|
const database = openDatabase({ path: ":memory:", busyTimeoutMs: 1000 });
|
|
const migrations = runMigrations(database);
|
|
const lifecycle = { ready: true };
|
|
const app = express();
|
|
const options = { config: config(), database, lifecycle, migrations, storage };
|
|
app.use("/health", createHealthRouter(options));
|
|
app.use("/api/health", createDetailedHealthRouter(options));
|
|
const server = app.listen(0, "127.0.0.1");
|
|
await new Promise((resolve) => server.once("listening", resolve));
|
|
|
|
try {
|
|
await callback(`http://127.0.0.1:${server.address().port}`);
|
|
} finally {
|
|
await new Promise((resolve) => server.close(resolve));
|
|
database.close();
|
|
}
|
|
}
|
|
|
|
test("storage degradation is reported but does not fail API readiness", async () => {
|
|
const storage = {
|
|
health() {
|
|
return {
|
|
status: "degraded",
|
|
checkedAt: "2026-08-16T12:00:00.000Z",
|
|
lastSuccessAt: "2026-08-16T11:59:00.000Z",
|
|
code: "STORAGE_UNAVAILABLE",
|
|
};
|
|
},
|
|
};
|
|
|
|
await withServer(storage, async (baseUrl) => {
|
|
const ready = await fetch(`${baseUrl}/health/ready`);
|
|
const readyBody = await ready.json();
|
|
assert.equal(ready.status, 200);
|
|
assert.equal(readyBody.status, "ready");
|
|
assert.equal(readyBody.checks.storage, "degraded");
|
|
|
|
const detailed = await fetch(`${baseUrl}/api/health`);
|
|
const detailedBody = await detailed.json();
|
|
assert.equal(detailed.status, 200);
|
|
assert.equal(detailedBody.status, "degraded");
|
|
assert.equal(detailedBody.services.storage.status, "degraded");
|
|
assert.equal(
|
|
detailedBody.services.storage.lastSuccessAt,
|
|
"2026-08-16T11:59:00.000Z",
|
|
);
|
|
assert.equal(detailedBody.services.storage.code, "STORAGE_UNAVAILABLE");
|
|
assert.doesNotMatch(JSON.stringify(detailedBody), /must-not-leak/);
|
|
assert.doesNotMatch(JSON.stringify(detailedBody), /private-storage/);
|
|
});
|
|
});
|
|
|
|
test("healthy storage produces healthy detailed status", async () => {
|
|
const storage = {
|
|
health() {
|
|
return { status: "healthy", checkedAt: "2026-08-16T12:00:00.000Z" };
|
|
},
|
|
};
|
|
|
|
await withServer(storage, async (baseUrl) => {
|
|
const detailed = await fetch(`${baseUrl}/api/health`);
|
|
const body = await detailed.json();
|
|
assert.equal(detailed.status, 200);
|
|
assert.equal(body.status, "ok");
|
|
assert.equal(body.services.storage.status, "healthy");
|
|
});
|
|
});
|