From 5d9729fefdd452b25ba9c4222fc0af16ec342b1a Mon Sep 17 00:00:00 2001 From: protonmatter Date: Tue, 29 Sep 2026 14:46:31 -0400 Subject: [PATCH 1/2] test: keep Orrery fixtures independent of current imagery --- tests/python/test_orrery_earth.py | 51 ++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/tests/python/test_orrery_earth.py b/tests/python/test_orrery_earth.py index 0236b278..d30602b8 100644 --- a/tests/python/test_orrery_earth.py +++ b/tests/python/test_orrery_earth.py @@ -9,6 +9,7 @@ import sys import tempfile import unittest +from unittest.mock import patch from urllib.parse import parse_qs, urlsplit ROOT = Path(__file__).resolve().parents[2] @@ -17,6 +18,7 @@ from tools import fetch_earth_reference as fetch from tools import validate_visual_assets as visual from test_fetch_earth_reference import PALETTE, capabilities +from test_mapped_references import reference NOW = datetime(2026, 9, 28, 12, tzinfo=timezone.utc) @@ -59,7 +61,16 @@ def checkout(self): data = json.loads((ROOT / "apps/web/visual-assets.v1.json").read_text(encoding="utf-8")) old = next(r for r in data["mapped_references"] if r["body"] == "Earth" and r["role"] == "weather") raw = b"previous raster fixture" - old.update(sha256=hashlib.sha256(raw).hexdigest(), bytes=len(raw)) + # Daily data changes must not move the synthetic migration's baseline. + # Start from a historical manual record; the tests below explicitly + # stage once when they need an existing automated daily reference. + baseline = reference(raw) + baseline.update(id="synthetic-earth-weather", role="weather", nodata="alpha", + path="textures/reference/earth-weather-20260912.png", dimensions=[2048, 1024], + source_url=fetch.BASE + "?TIME=2026-09-12", + observation_label="12 September 2026 synthetic reference") + data["mapped_references"][data["mapped_references"].index(old)] = baseline + old = baseline target = web / old["path"]; target.parent.mkdir(parents=True); target.write_bytes(raw) (web / "visual-assets.v1.json").write_text(json.dumps(data), encoding="utf-8") (web / "js/visualAssetManifest.js").write_text(visual.browser_module(data), encoding="utf-8") @@ -73,6 +84,34 @@ def test_accepts_rederived_pixels_without_editing_candidate(self): self.assertEqual(result.sha256, hashlib.sha256(before["weather-rgba.png"]).hexdigest()) self.assertEqual(before, {p.name: p.read_bytes() for p in self.candidate.iterdir()}) + def test_fixture_migration_is_independent_of_newer_live_weather(self): + source = json.loads((ROOT / "apps/web/visual-assets.v1.json").read_text(encoding="utf-8")) + for daily in (False, True): + with self.subTest(daily=daily), tempfile.TemporaryDirectory() as current, tempfile.TemporaryDirectory() as work: + data = deepcopy(source) + weather = self.mod.get_weather(data) + weather.pop("automated_refresh", None) + weather.update(source_url=fetch.BASE + "?TIME=2030-01-01", + source_retrieved_at="2030-01-02T00:00:00Z", reviewed_at="2030-01-02T01:00:00Z", + observation_label="1 January 2030 synthetic accepted reference", + path="textures/reference/earth-weather-20300101.png") + weather["derivation_inputs"][0]["url"] = weather["source_url"] + if daily: + weather.update(path="textures/reference/earth-weather-daily.png", reviewed_at=None, + automated_refresh={"recipe_id": "earth-modis-terra-aqua.v1", + "data_date": "2030-01-01", "validated_at": "2030-01-02T01:00:00Z", + "source_manifest_sha256": "a" * 64, "semantic_id": "b" * 64}) + visual.validate_inventory(data) + path = Path(current) / "apps/web/visual-assets.v1.json" + path.parent.mkdir(parents=True); path.write_text(json.dumps(data), encoding="utf-8") + with patch.dict(globals(), ROOT=Path(current)), patch.object(self.temp, "name", work): + root, _ = self.checkout() + result = self.mod.stage_candidate(root, self.candidate, NOW, apply=True) + self.assertEqual(result["state"], "validated") + self.assertIn("apps/web/textures/reference/earth-weather-20260912.png", result["changed_paths"]) + self.assertFalse((root / "apps/web/textures/reference/earth-weather-20260912.png").exists()) + self.assertEqual(self.mod.stage_candidate(root, self.candidate, NOW)["state"], "no-op") + def test_candidate_json_rejects_overflow_nested_duplicates_and_depth_without_echoing_payload(self): manifest = self.candidate / "earth-reference.json" original = manifest.read_bytes().rstrip()[:-1] @@ -145,8 +184,9 @@ def test_retrieval_and_capabilities_changes_do_not_create_refreshes(self): self.assertEqual(result["state"], "no-op"); self.assertEqual(result["changed_paths"], []) def test_inventory_transition_rejects_unrelated_edits(self): - root, _ = self.checkout(); self.mod.stage_candidate(root, self.candidate, NOW, apply=True) - base = (ROOT / "apps/web/visual-assets.v1.json").read_bytes() + root, _ = self.checkout() + base = (root / "apps/web/visual-assets.v1.json").read_bytes() + self.mod.stage_candidate(root, self.candidate, NOW, apply=True) web = root / "apps/web"; candidate = (web / "visual-assets.v1.json").read_bytes() png = (web / "textures/reference/earth-weather-daily.png").read_bytes() module = (web / "js/visualAssetManifest.js").read_bytes() @@ -155,8 +195,9 @@ def test_inventory_transition_rejects_unrelated_edits(self): with self.assertRaises(ValueError): self.mod.validate_inventory_transition(base, json.dumps(data).encode(), png, visual.browser_module(data).encode()) def test_transition_rejects_relabelled_machine_recipe_provenance(self): - root, _ = self.checkout(); self.mod.stage_candidate(root, self.candidate, NOW, apply=True) - base = (ROOT / "apps/web/visual-assets.v1.json").read_bytes() + root, _ = self.checkout() + base = (root / "apps/web/visual-assets.v1.json").read_bytes() + self.mod.stage_candidate(root, self.candidate, NOW, apply=True) web = root / "apps/web"; original = json.loads((web / "visual-assets.v1.json").read_text(encoding="utf-8")) png = (web / "textures/reference/earth-weather-daily.png").read_bytes() changes = [lambda r: r["automated_refresh"].update(semantic_id="a" * 64), From 869ef946138f77d1ae4297f01bd547cd47fc65c1 Mon Sep 17 00:00:00 2001 From: protonmatter Date: Tue, 29 Sep 2026 14:50:46 -0400 Subject: [PATCH 2/2] test: assert the selected daily Earth observation label --- tests/web/planetAppearanceRuntime.test.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/web/planetAppearanceRuntime.test.mjs b/tests/web/planetAppearanceRuntime.test.mjs index 39f7a3f1..12b7589c 100644 --- a/tests/web/planetAppearanceRuntime.test.mjs +++ b/tests/web/planetAppearanceRuntime.test.mjs @@ -142,11 +142,11 @@ test("Earth defaults to the complete historical composite; swaths require an exp const epoch = h.state.renderUnix, bodies = JSON.stringify(h.state.bodies); assert.equal(bodyDraw(h, paint(h)).textures.get(3), reference.texture); assert.match(h.nodes.orreryEarthLayerStatus.textContent, /2002/); - assert.doesNotMatch(h.nodes.orreryEarthLayerStatus.textContent, /12 September 2026/); + assert.ok(!h.nodes.orreryEarthLayerStatus.textContent.includes(appearanceReference('Earth','weather').observation_label)); h.input("orreryEarthCloudSource", "daily", "change"); const daily = complete(h, "Earth", "weather"); assert.equal(bodyDraw(h, paint(h)).textures.get(3), daily.texture); - assert.match(h.nodes.orreryEarthLayerStatus.textContent, /12 September 2026/); + assert.ok(h.nodes.orreryEarthLayerStatus.textContent.includes(appearanceReference('Earth','weather').observation_label)); assert.match(h.nodes.orreryEarthLayerStatus.textContent, /swath.*gaps|gaps.*swath/i); h.input("orreryEarthCloudSource", "composite", "change"); assert.equal(bodyDraw(h, paint(h)).textures.get(3), reference.texture); @@ -192,7 +192,7 @@ test("explicit daily imagery rejects a late cancelled composite without substitu assert.equal(bodyDraw(h, paint(h)).uniforms.u_earthWeather, 0, "late completion of an unselected source cannot replace the selection"); daily.image.onerror(); assert.equal(bodyDraw(h, paint(h)).uniforms.u_earthWeather, 0); - assert.match(h.nodes.orreryEarthLayerStatus.textContent, /12 September 2026.*unavailable/); + assert.ok(h.nodes.orreryEarthLayerStatus.textContent.includes(`${appearanceReference('Earth','weather').observation_label} ยท unavailable`)); assert.doesNotMatch(h.nodes.orreryEarthLayerStatus.textContent, /2002/); h.input("orreryEarthCloudSource", "composite", "change"); complete(h,'Earth','cloud-composite');