SPB Git

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%
4.4 KB · 124 lines typescript
Raw Blame History
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