import json import pytest from player.ring import RingMove, ScaleRing, TransitionStep, advance_ring from simulator.clips import ( Clip, load_manifest, load_ring, ring_move_to_dict, ring_to_dict, ) def _manifest_dict(): return { "clips": [ { "id": "forest", "title": "Yosemite Falls (neutral)", "base_file": "forest/base.mp4", "license": "poc-sample", "source": "hef-poc", "right_variants": { "4": {"file": "forest/right4.mp4", "model": "sd-turbo+flow"}, "1": {"file": "forest/right1.mp4"}, }, "annotations": [ {"key": "detected.water", "box": [0.1, 0.2, 0.3, 0.4], "min_level": 1}, {"key": "detected.conifer", "box": [0.6, 0.1, 0.2, 0.2], "min_level": 3}, ], "strings": {"en": {"detected.water": "flowing water", "detected.conifer": "conifer"}}, } ] } def test_load_manifest_parses_clips(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_manifest_dict())) clips = load_manifest(p) assert len(clips) == 1 c = clips[0] assert isinstance(c, Clip) assert c.id == "forest" assert c.base_file == "forest/base.mp4" def test_clip_lists_variant_files_by_strength(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_manifest_dict())) c = load_manifest(p)[0] # variant 0 is always the raw base; authored strengths come from the manifest assert c.variant_file(0) == "forest/base.mp4" assert c.variant_file(4) == "forest/right4.mp4" assert c.variant_file(1) == "forest/right1.mp4" # an unauthored strength falls back to the raw base assert c.variant_file(2) == "forest/base.mp4" def test_clip_serializes_to_dict_for_the_api(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_manifest_dict())) d = load_manifest(p)[0].to_dict() assert d["id"] == "forest" assert d["base_file"] == "forest/base.mp4" assert d["annotations"][0]["key"] == "detected.water" assert d["strings"]["en"]["detected.water"] == "flowing water" # variant map is exposed keyed by strength string, including 0 -> base assert d["right_variants"]["0"]["file"] == "forest/base.mp4" assert d["right_variants"]["4"]["file"] == "forest/right4.mp4" def test_missing_manifest_raises(tmp_path): with pytest.raises(FileNotFoundError): load_manifest(tmp_path / "nope.json") def _ring_manifest_dict(): return { "clips": [], "ring": { "scales": [ {"id": "cosmos", "clip_id": "cosmos"}, {"id": "forest", "clip_id": "forest"}, {"id": "abyss", "clip_id": "abyss"}, ], "transitions": [ {"file": "transitions/cosmos-forest.mp4", "model": "placeholder"}, {"file": "transitions/forest-abyss.mp4", "model": "placeholder"}, {"file": "transitions/abyss-cosmos.mp4", "model": "placeholder"}, ], }, } def test_load_ring_builds_a_scale_ring(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_ring_manifest_dict())) ring = load_ring(p) assert isinstance(ring, ScaleRing) assert len(ring) == 3 assert ring.scales[0].id == "cosmos" assert ring.scales[0].clip_id == "cosmos" assert ring.transitions[2].file == "transitions/abyss-cosmos.mp4" assert ring.transitions[0].model == "placeholder" def test_load_ring_is_none_when_absent(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_manifest_dict())) # no "ring" key assert load_ring(p) is None def test_ring_to_dict_carries_scale_titles_from_clips(tmp_path): p = tmp_path / "manifest.json" md = _ring_manifest_dict() md["clips"] = [ {"id": "cosmos", "title": "NASA cosmos", "base_file": "cosmos/base.mp4"}, {"id": "forest", "title": "Yosemite", "base_file": "forest/base.mp4"}, {"id": "abyss", "title": "NOAA abyss", "base_file": "abyss/base.mp4"}, ] p.write_text(json.dumps(md)) ring = load_ring(p) clips = load_manifest(p) d = ring_to_dict(ring, clips) assert [s["id"] for s in d["scales"]] == ["cosmos", "forest", "abyss"] assert d["scales"][0]["title"] == "NASA cosmos" assert d["scales"][0]["clip_id"] == "cosmos" assert d["transitions"][2]["file"] == "transitions/abyss-cosmos.mp4" def test_ring_move_to_dict_serializes_steps(tmp_path): p = tmp_path / "manifest.json" p.write_text(json.dumps(_ring_manifest_dict())) ring = load_ring(p) move = advance_ring(ring, from_index=2, delta=1) # abyss -> wrap -> cosmos d = ring_move_to_dict(move, ring) assert d["from_index"] == 2 assert d["to_index"] == 0 assert d["wrapped"] is True assert d["target_clip_id"] == "cosmos" assert d["steps"][0]["file"] == "transitions/abyss-cosmos.mp4" assert d["steps"][0]["reversed"] is False assert d["steps"][0]["to_index"] == 0