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%
3.0 KB · 76 lines typescript
Raw Blame History
1/**2 * earth-now.co3 * Author:  Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File:    apps/ingest/test/noaa-co2.test.ts6 * Purpose: NOAA GML co2_mm_mlo parser/fetcher tests — fixture-backed incl. missing month + truncated file, no network7 */89import { readFileSync } from "node:fs";10import { describe, expect, it } from "vitest";11import type { FetchLike } from "../src/fetch-like";12import { NOAA_CO2_URL, fetchNoaaCo2, parseNoaaMonthlyCo2 } from "../src/sources/noaa-co2";1314const fixture = (name: string): string =>15  readFileSync(new URL(`./fixtures/${name}`, import.meta.url), "utf8");1617describe("parseNoaaMonthlyCo2", () => {18  const sample = fixture("co2_mm_mlo_sample.txt");1920  it("parses the sample: 16 data rows, one -9.99 missing month skipped → 15 observations", () => {21    const obs = parseNoaaMonthlyCo2(sample);22    expect(obs).toHaveLength(15);23  });2425  it("pins each observation to the 15th of the month UTC with the right value", () => {26    const obs = parseNoaaMonthlyCo2(sample);27    expect(obs[0]).toEqual({ time: "2024-01-15T00:00:00.000Z", value: 422.8 });28    expect(obs[obs.length - 1]).toEqual({ time: "2025-04-15T00:00:00.000Z", value: 429.64 });29  });3031  it("skips the missing month (2024-06, average -9.99) without inventing a value", () => {32    const obs = parseNoaaMonthlyCo2(sample);33    expect(obs.some((o) => o.time.startsWith("2024-06"))).toBe(false);34    // May and July around the gap are both present.35    expect(obs.some((o) => o.time.startsWith("2024-05"))).toBe(true);36    expect(obs.some((o) => o.time.startsWith("2024-07"))).toBe(true);37  });3839  it("THROWS on the truncated fixture (chosen degraded behavior — never feed partial files downstream)", () => {40    expect(() => parseNoaaMonthlyCo2(fixture("co2_mm_mlo_truncated.txt"))).toThrow(41      /expected 8 columns.*truncated/,42    );43  });4445  it("throws when there are no data rows at all", () => {46    expect(() => parseNoaaMonthlyCo2("# only comments\n# nothing else\n")).toThrow(47      /no valid data rows/,48    );49  });5051  it("throws on out-of-range year/month", () => {52    const bad = "1800  1  1800.04  350.00  350.00  31  0.4  0.1";53    expect(() => parseNoaaMonthlyCo2(bad)).toThrow(/invalid year/);54    const badMonth = "2024 13  2024.99  420.00  420.00  31  0.4  0.1";55    expect(() => parseNoaaMonthlyCo2(badMonth)).toThrow(/invalid month/);56  });57});5859describe("fetchNoaaCo2", () => {60  it("fetches the NOAA URL and parses via an injected fetchImpl (no network)", async () => {61    const seen: string[] = [];62    const fetchImpl: FetchLike = async (url) => {63      seen.push(url);64      return { ok: true, status: 200, text: async () => fixture("co2_mm_mlo_sample.txt") };65    };66    const obs = await fetchNoaaCo2(fetchImpl);67    expect(obs).toHaveLength(15);68    expect(seen).toEqual([NOAA_CO2_URL]);69  });7071  it("throws on HTTP errors", async () => {72    const fetchImpl: FetchLike = async () => ({ ok: false, status: 500, text: async () => "" });73    await expect(fetchNoaaCo2(fetchImpl)).rejects.toThrow(/HTTP 500/);74  });75});76