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/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