From f119e484c933fc46b5436b2c590a9cfad8e53594 Mon Sep 17 00:00:00 2001 From: Zheng Li Date: Thu, 16 Apr 2026 18:28:08 +0800 Subject: [PATCH] feat(live): upgrade /api/live to surety standard Add component, timestamp, uptime fields and Cache-Control: no-store header. Extract version to lib/version.ts for reuse. Closes #4 --- src/lib/version.ts | 3 +++ src/server.ts | 13 +++++++++++-- tests/unit/health.test.ts | 13 ++++++++++--- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 src/lib/version.ts diff --git a/src/lib/version.ts b/src/lib/version.ts new file mode 100644 index 0000000..92e885c --- /dev/null +++ b/src/lib/version.ts @@ -0,0 +1,3 @@ +import pkg from "../../package.json" with { type: "json" }; + +export const version = `v${pkg.version}`; diff --git a/src/server.ts b/src/server.ts index d85b9f7..c4e43b9 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,13 +1,15 @@ import { Hono } from "hono"; import { extractClientIp } from "./utils/ip.js"; import { lookupIp, type LookupResult } from "./services/ipLookup.js"; -import pkg from "../package.json" with { type: "json" }; +import { version } from "./lib/version.js"; type LookupFn = (ip: string | null) => Promise; const attribution = "IP2Region data provided by https://ip2region.net (Apache-2.0)."; +const bootedAt = Date.now(); + export function createApp( lookup: LookupFn = lookupIp, apiKey: string | undefined = process.env.ECHO_API_KEY, @@ -15,7 +17,14 @@ export function createApp( const app = new Hono(); app.get("/api/live", (c) => { - return c.json({ status: "ok", version: `v${pkg.version}` }); + c.header("Cache-Control", "no-store"); + return c.json({ + status: "ok", + version, + component: "echo", + timestamp: new Date().toISOString(), + uptime: Math.round((Date.now() - bootedAt) / 1000), + }); }); app.get("/", (c) => { diff --git a/tests/unit/health.test.ts b/tests/unit/health.test.ts index 4f448aa..214ec63 100644 --- a/tests/unit/health.test.ts +++ b/tests/unit/health.test.ts @@ -1,18 +1,25 @@ import { describe, expect, test } from "bun:test"; import { createApp } from "../../src/server.js"; -import pkg from "../../package.json" with { type: "json" }; +import { version } from "../../src/lib/version.js"; const testApiKey = "test-secret-key"; describe("GET /api/live", () => { - test("returns ok status and version", async () => { + test("returns surety-standard health response", async () => { const app = createApp(); const res = await app.request("/api/live"); expect(res.status).toBe(200); + expect(res.headers.get("cache-control")).toBe("no-store"); const body = await res.json(); - expect(body).toEqual({ status: "ok", version: `v${pkg.version}` }); + expect(body.status).toBe("ok"); + expect(body.version).toBe(version); + expect(body.component).toBe("echo"); + expect(typeof body.timestamp).toBe("string"); + expect(new Date(body.timestamp).toISOString()).toBe(body.timestamp); + expect(typeof body.uptime).toBe("number"); + expect(body.uptime).toBeGreaterThanOrEqual(0); }); });