"""ORCH-6: tests for the project registry (src/projects.py). Covers resolvers (by plane_id, by repo, unknown -> None, known ids) against the built-in default registry, plus ORCH_PROJECTS_JSON parsing (valid + malformed -> default fallback). The pure parser ``_parse_projects_json`` is tested directly so we don't mutate the module-global registry. Resolver tests run against the default registry; if another test (e.g. test_webhooks) set ORCH_PROJECTS_JSON in the env, we restore the default via monkeypatch + reload_projects to keep this file order-independent. """ import pytest from src import projects as P from src.projects import ( ProjectConfig, get_project_by_plane_id, get_project_by_repo, known_plane_project_ids, reload_projects, _parse_projects_json, _DEFAULT_PROJECTS, ) # Known ids from the default registry / task spec. ENDURO_PLANE_ID = "7a79f0a9-5278-49cd-9007-9a338f238f9c" ORCH_PLANE_ID = "8da6aa25-a60e-44d6-a1e2-d8ae59aa7d6a" @pytest.fixture def default_registry(monkeypatch): """Force the default (built-in) registry regardless of ORCH_PROJECTS_JSON that other test modules may have set in the process env.""" monkeypatch.setattr(P.settings, "projects_json", "") reload_projects() yield # Restore from current settings (whatever env says) after the test. reload_projects() # --------------------------------------------------------------------------- # Resolvers # --------------------------------------------------------------------------- def test_get_project_by_plane_id_orchestrator(default_registry): proj = get_project_by_plane_id(ORCH_PLANE_ID) assert proj is not None assert proj.repo == "orchestrator" assert proj.work_item_prefix == "ORCH" assert proj.plane_project_id == ORCH_PLANE_ID def test_get_project_by_plane_id_enduro(default_registry): proj = get_project_by_plane_id(ENDURO_PLANE_ID) assert proj is not None assert proj.repo == "enduro-trails" assert proj.work_item_prefix == "ET" def test_get_project_by_plane_id_unknown_returns_none(default_registry): assert get_project_by_plane_id("00000000-0000-0000-0000-000000000000") is None def test_get_project_by_plane_id_empty_returns_none(default_registry): assert get_project_by_plane_id("") is None assert get_project_by_plane_id(None) is None def test_get_project_by_repo(default_registry): assert get_project_by_repo("enduro-trails").work_item_prefix == "ET" assert get_project_by_repo("orchestrator").work_item_prefix == "ORCH" def test_get_project_by_repo_unknown_returns_none(default_registry): assert get_project_by_repo("does-not-exist") is None assert get_project_by_repo("") is None assert get_project_by_repo(None) is None def test_known_plane_project_ids(default_registry): ids = known_plane_project_ids() assert isinstance(ids, set) assert ENDURO_PLANE_ID in ids assert ORCH_PLANE_ID in ids assert len(ids) == len(_DEFAULT_PROJECTS) # --------------------------------------------------------------------------- # ORCH_PROJECTS_JSON parsing (pure function, no global mutation) # --------------------------------------------------------------------------- def test_parse_empty_returns_none(): assert _parse_projects_json("") is None assert _parse_projects_json(" ") is None assert _parse_projects_json(None) is None def test_parse_valid_json(): raw = ( '[{"plane_project_id": "p-1", "repo": "repo-a", ' '"work_item_prefix": "AAA", "name": "Alpha"}]' ) parsed = _parse_projects_json(raw) assert parsed is not None assert len(parsed) == 1 assert isinstance(parsed[0], ProjectConfig) assert parsed[0].plane_project_id == "p-1" assert parsed[0].repo == "repo-a" assert parsed[0].work_item_prefix == "AAA" assert parsed[0].name == "Alpha" def test_parse_valid_json_multiple(): raw = ( '[{"plane_project_id": "p-1", "repo": "repo-a", "work_item_prefix": "A"},' ' {"plane_project_id": "p-2", "repo": "repo-b", "work_item_prefix": "B"}]' ) parsed = _parse_projects_json(raw) assert len(parsed) == 2 # name defaults to repo when omitted assert parsed[0].name == "repo-a" assert parsed[1].repo == "repo-b" def test_parse_malformed_json_returns_none(): assert _parse_projects_json("{not valid json") is None assert _parse_projects_json("[}") is None def test_parse_not_an_array_returns_none(): # A JSON object (not array) is invalid -> fallback. assert _parse_projects_json('{"plane_project_id": "p-1"}') is None def test_parse_skips_bad_entries_keeps_good(): raw = ( '[{"repo": "missing-id"},' # missing required key -> skipped ' {"plane_project_id": "p-2", "repo": "repo-b", "work_item_prefix": "B"}]' ) parsed = _parse_projects_json(raw) assert parsed is not None assert len(parsed) == 1 assert parsed[0].plane_project_id == "p-2" def test_parse_all_bad_entries_returns_none(): # No valid entries -> None (fallback to default). assert _parse_projects_json('[{"repo": "no-id"}, "not-an-object"]') is None def test_reload_from_custom_json(monkeypatch): """End-to-end: set settings.projects_json, reload, resolvers reflect it.""" custom = ( '[{"plane_project_id": "custom-uuid", "repo": "custom-repo", ' '"work_item_prefix": "CUS", "name": "Custom"}]' ) monkeypatch.setattr(P.settings, "projects_json", custom) reload_projects() try: assert get_project_by_plane_id("custom-uuid").repo == "custom-repo" assert get_project_by_repo("custom-repo").work_item_prefix == "CUS" assert known_plane_project_ids() == {"custom-uuid"} # The built-in defaults must NOT be present when JSON overrides. assert get_project_by_plane_id(ENDURO_PLANE_ID) is None finally: reload_projects() def test_reload_invalid_json_falls_back_to_default(monkeypatch): monkeypatch.setattr(P.settings, "projects_json", "{garbage") reload_projects() try: assert get_project_by_plane_id(ENDURO_PLANE_ID) is not None assert get_project_by_plane_id(ORCH_PLANE_ID) is not None finally: reload_projects()