spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: apps/api/test/api.test.ts6 * Purpose: API integration tests via app.inject() — health, registry, models, 404s, badge SVG (pollers disabled)7 */89// Pollers read the env at buildApp() time, so setting it here (after hoisted10// imports evaluate) is safe; buildApp({ pollers: false }) is the second guard.11process.env.ENABLE_RT_POLLERS = "0";1213import { afterAll, beforeAll, describe, expect, it } from "vitest";14import { type CounterModel, counterValue } from "@earth-now/counter";15import { buildApp } from "../src/server";1617type App = Awaited<ReturnType<typeof buildApp>>;1819interface HealthBody {20 status: string;21 now: string;22 modelCount: number;23 blockedCount: number;24 uptimeSeconds: number;25}2627interface MetricSummary {28 id: string;29 stale: boolean;30 modelVersion?: string;31 observedAt?: string;32 sources: Array<{ id: string; license: string }>;33}3435let app: App;3637beforeAll(async () => {38 app = await buildApp({ logger: false, pollers: false });39});4041afterAll(async () => {42 await app.close();43});4445describe("GET /api/health", () => {46 it("returns ok with model/blocked counts and uptime", async () => {47 const res = await app.inject({ method: "GET", url: "/api/health" });48 expect(res.statusCode).toBe(200);49 const body = JSON.parse(res.body) as HealthBody;50 expect(body.status).toBe("ok");51 expect(Number.isNaN(Date.parse(body.now))).toBe(false);52 expect(body.modelCount).toBeGreaterThan(0);53 expect(body.blockedCount).toBeGreaterThanOrEqual(0);54 expect(body.uptimeSeconds).toBeGreaterThanOrEqual(0);55 });56});5758describe("GET /v1/metrics", () => {59 it("returns the full registry with source attribution and stale flags", async () => {60 const res = await app.inject({ method: "GET", url: "/v1/metrics" });61 expect(res.statusCode).toBe(200);62 const metrics = JSON.parse(res.body) as MetricSummary[];63 expect(metrics.length).toBeGreaterThanOrEqual(30);64 expect(metrics.map((m) => m.id)).toContain("world_population");65 for (const metric of metrics) {66 expect(typeof metric.stale).toBe("boolean");67 expect(metric.sources.length).toBeGreaterThan(0);68 for (const source of metric.sources) expect(source.license.length).toBeGreaterThan(0);69 }70 });71});7273describe("GET /v1/metrics/:id/model", () => {74 it("serves a world_population model evaluating to a sane live value", async () => {75 const res = await app.inject({ method: "GET", url: "/v1/metrics/world_population/model" });76 expect(res.statusCode).toBe(200);77 const model = JSON.parse(res.body) as CounterModel;78 expect(model.metricId).toBe("world_population");79 const value = counterValue(model, Date.now());80 expect(value).toBeGreaterThan(8.2e9);81 expect(value).toBeLessThan(8.4e9);82 });8384 it("returns 404 for an unknown metric id", async () => {85 const res = await app.inject({ method: "GET", url: "/v1/metrics/nope_not_a_metric/model" });86 expect(res.statusCode).toBe(404);87 });88});8990describe("GET /badge/:id.svg", () => {91 it("renders an SVG badge for co2_ppm with CDN caching headers", async () => {92 const res = await app.inject({ method: "GET", url: "/badge/co2_ppm.svg" });93 expect(res.statusCode).toBe(200);94 expect(res.headers["content-type"]).toContain("image/svg+xml");95 expect(res.headers["cache-control"]).toBe("public, max-age=60");96 expect(res.body).toContain("<svg");97 expect(res.body).toContain("earth-now.co");98 });99100 it("returns 404 for an unknown metric badge", async () => {101 const res = await app.inject({ method: "GET", url: "/badge/nope_not_a_metric.svg" });102 expect(res.statusCode).toBe(404);103 });104});105106describe("GET /v1/models", () => {107 it("serves every fitted model (registry count minus blocked)", async () => {108 const [modelsRes, metricsRes, healthRes] = await Promise.all([109 app.inject({ method: "GET", url: "/v1/models" }),110 app.inject({ method: "GET", url: "/v1/metrics" }),111 app.inject({ method: "GET", url: "/api/health" }),112 ]);113 expect(modelsRes.statusCode).toBe(200);114 const { models, generatedAt } = JSON.parse(modelsRes.body) as {115 models: Record<string, CounterModel>;116 generatedAt: string;117 };118 const metrics = JSON.parse(metricsRes.body) as MetricSummary[];119 const health = JSON.parse(healthRes.body) as HealthBody;120 expect(Number.isNaN(Date.parse(generatedAt))).toBe(false);121 expect(Object.keys(models).length).toBe(metrics.length - health.blockedCount);122 });123});124