/** * earth-now.co * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: apps/api/test/api.test.ts * Purpose: API integration tests via app.inject() — health, registry, models, 404s, badge SVG (pollers disabled) */ // Pollers read the env at buildApp() time, so setting it here (after hoisted // imports evaluate) is safe; buildApp({ pollers: false }) is the second guard. process.env.ENABLE_RT_POLLERS = "0"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { type CounterModel, counterValue } from "@earth-now/counter"; import { buildApp } from "../src/server"; type App = Awaited>; interface HealthBody { status: string; now: string; modelCount: number; blockedCount: number; uptimeSeconds: number; } interface MetricSummary { id: string; stale: boolean; modelVersion?: string; observedAt?: string; sources: Array<{ id: string; license: string }>; } let app: App; beforeAll(async () => { app = await buildApp({ logger: false, pollers: false }); }); afterAll(async () => { await app.close(); }); describe("GET /api/health", () => { it("returns ok with model/blocked counts and uptime", async () => { const res = await app.inject({ method: "GET", url: "/api/health" }); expect(res.statusCode).toBe(200); const body = JSON.parse(res.body) as HealthBody; expect(body.status).toBe("ok"); expect(Number.isNaN(Date.parse(body.now))).toBe(false); expect(body.modelCount).toBeGreaterThan(0); expect(body.blockedCount).toBeGreaterThanOrEqual(0); expect(body.uptimeSeconds).toBeGreaterThanOrEqual(0); }); }); describe("GET /v1/metrics", () => { it("returns the full registry with source attribution and stale flags", async () => { const res = await app.inject({ method: "GET", url: "/v1/metrics" }); expect(res.statusCode).toBe(200); const metrics = JSON.parse(res.body) as MetricSummary[]; expect(metrics.length).toBeGreaterThanOrEqual(30); expect(metrics.map((m) => m.id)).toContain("world_population"); for (const metric of metrics) { expect(typeof metric.stale).toBe("boolean"); expect(metric.sources.length).toBeGreaterThan(0); for (const source of metric.sources) expect(source.license.length).toBeGreaterThan(0); } }); }); describe("GET /v1/metrics/:id/model", () => { it("serves a world_population model evaluating to a sane live value", async () => { const res = await app.inject({ method: "GET", url: "/v1/metrics/world_population/model" }); expect(res.statusCode).toBe(200); const model = JSON.parse(res.body) as CounterModel; expect(model.metricId).toBe("world_population"); const value = counterValue(model, Date.now()); expect(value).toBeGreaterThan(8.2e9); expect(value).toBeLessThan(8.4e9); }); it("returns 404 for an unknown metric id", async () => { const res = await app.inject({ method: "GET", url: "/v1/metrics/nope_not_a_metric/model" }); expect(res.statusCode).toBe(404); }); }); describe("GET /badge/:id.svg", () => { it("renders an SVG badge for co2_ppm with CDN caching headers", async () => { const res = await app.inject({ method: "GET", url: "/badge/co2_ppm.svg" }); expect(res.statusCode).toBe(200); expect(res.headers["content-type"]).toContain("image/svg+xml"); expect(res.headers["cache-control"]).toBe("public, max-age=60"); expect(res.body).toContain(" { const res = await app.inject({ method: "GET", url: "/badge/nope_not_a_metric.svg" }); expect(res.statusCode).toBe(404); }); }); describe("GET /v1/models", () => { it("serves every fitted model (registry count minus blocked)", async () => { const [modelsRes, metricsRes, healthRes] = await Promise.all([ app.inject({ method: "GET", url: "/v1/models" }), app.inject({ method: "GET", url: "/v1/metrics" }), app.inject({ method: "GET", url: "/api/health" }), ]); expect(modelsRes.statusCode).toBe(200); const { models, generatedAt } = JSON.parse(modelsRes.body) as { models: Record; generatedAt: string; }; const metrics = JSON.parse(metricsRes.body) as MetricSummary[]; const health = JSON.parse(healthRes.body) as HealthBody; expect(Number.isNaN(Date.parse(generatedAt))).toBe(false); expect(Object.keys(models).length).toBe(metrics.length - health.blockedCount); }); });