"""Tests for project registry resolution and refresh behavior.""" import json import atocore.config as config from atocore.projects.registry import ( build_project_registration_proposal, get_registered_project, get_project_registry_template, list_registered_projects, register_project, refresh_registered_project, ) def test_project_registry_lists_projects_with_resolved_roots(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" vault_dir.mkdir() drive_dir.mkdir() config_dir.mkdir() (vault_dir / "incoming" / "projects" / "p04-gigabit").mkdir(parents=True) registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p04-gigabit", "aliases": ["p04", "gigabit"], "description": "P04 docs", "ingest_roots": [ { "source": "vault", "subpath": "incoming/projects/p04-gigabit", "label": "P04 staged docs", } ], } ] } ), encoding="utf-8", ) monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() projects = list_registered_projects() finally: config.settings = original_settings assert len(projects) == 1 assert projects[0]["id"] == "p04-gigabit" assert projects[0]["ingest_roots"][0]["exists"] is True def test_project_registry_resolves_alias(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" vault_dir.mkdir() drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p05-interferometer", "aliases": ["p05", "interferometer"], "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p05-interferometer"} ], } ] } ), encoding="utf-8", ) monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() project = get_registered_project("p05") finally: config.settings = original_settings assert project is not None assert project.project_id == "p05-interferometer" def test_refresh_registered_project_ingests_registered_roots(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" project_dir = vault_dir / "incoming" / "projects" / "p06-polisher" project_dir.mkdir(parents=True) drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p06-polisher", "aliases": ["p06", "polisher"], "description": "P06 docs", "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p06-polisher"} ], } ] } ), encoding="utf-8", ) calls = [] def fake_ingest_folder(path, purge_deleted=True): calls.append((str(path), purge_deleted)) return [{"file": str(path / "README.md"), "status": "ingested"}] monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() monkeypatch.setattr("atocore.projects.registry.ingest_folder", fake_ingest_folder) result = refresh_registered_project("polisher") finally: config.settings = original_settings assert result["project"] == "p06-polisher" assert len(calls) == 1 assert calls[0][0].endswith("p06-polisher") assert calls[0][1] is False assert result["roots"][0]["status"] == "ingested" def test_project_registry_template_has_expected_shape(): template = get_project_registry_template() assert "projects" in template assert template["projects"][0]["id"] == "p07-example" assert template["projects"][0]["ingest_roots"][0]["source"] == "vault" def test_project_registry_rejects_alias_collision(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" vault_dir.mkdir() drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p04-gigabit", "aliases": ["shared"], "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p04-gigabit"} ], }, { "id": "p05-interferometer", "aliases": ["shared"], "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p05-interferometer"} ], }, ] } ), encoding="utf-8", ) monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() try: list_registered_projects() except ValueError as exc: assert "collision" in str(exc) else: raise AssertionError("Expected project registry collision to raise") finally: config.settings = original_settings def test_project_registration_proposal_normalizes_and_resolves_paths(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" staged = vault_dir / "incoming" / "projects" / "p07-example" staged.mkdir(parents=True) drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text(json.dumps({"projects": []}), encoding="utf-8") monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() proposal = build_project_registration_proposal( project_id="p07-example", aliases=["p07", "example-project", "p07"], description="Example project", ingest_roots=[ { "source": "vault", "subpath": "incoming/projects/p07-example", "label": "Primary docs", } ], ) finally: config.settings = original_settings assert proposal["project"]["aliases"] == ["p07", "example-project"] assert proposal["resolved_ingest_roots"][0]["exists"] is True assert proposal["valid"] is True def test_project_registration_proposal_reports_collisions(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" vault_dir.mkdir() drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p05-interferometer", "aliases": ["p05", "interferometer"], "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p05-interferometer"} ], } ] } ), encoding="utf-8", ) monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() proposal = build_project_registration_proposal( project_id="p08-example", aliases=["interferometer"], ingest_roots=[ {"source": "vault", "subpath": "incoming/projects/p08-example"} ], ) finally: config.settings = original_settings assert proposal["valid"] is False assert proposal["collisions"][0]["existing_project"] == "p05-interferometer" def test_register_project_persists_new_entry(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" staged = vault_dir / "incoming" / "projects" / "p07-example" staged.mkdir(parents=True) drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text(json.dumps({"projects": []}), encoding="utf-8") monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() result = register_project( project_id="p07-example", aliases=["p07", "example-project"], description="Example project", ingest_roots=[ { "source": "vault", "subpath": "incoming/projects/p07-example", "label": "Primary docs", } ], ) finally: config.settings = original_settings assert result["status"] == "registered" payload = json.loads(registry_path.read_text(encoding="utf-8")) assert payload["projects"][0]["id"] == "p07-example" assert payload["projects"][0]["aliases"] == ["p07", "example-project"] def test_register_project_rejects_collisions(tmp_path, monkeypatch): vault_dir = tmp_path / "vault" drive_dir = tmp_path / "drive" config_dir = tmp_path / "config" vault_dir.mkdir() drive_dir.mkdir() config_dir.mkdir() registry_path = config_dir / "project-registry.json" registry_path.write_text( json.dumps( { "projects": [ { "id": "p05-interferometer", "aliases": ["p05", "interferometer"], "ingest_roots": [ {"source": "vault", "subpath": "incoming/projects/p05-interferometer"} ], } ] } ), encoding="utf-8", ) monkeypatch.setenv("ATOCORE_VAULT_SOURCE_DIR", str(vault_dir)) monkeypatch.setenv("ATOCORE_DRIVE_SOURCE_DIR", str(drive_dir)) monkeypatch.setenv("ATOCORE_PROJECT_REGISTRY_PATH", str(registry_path)) original_settings = config.settings try: config.settings = config.Settings() try: register_project( project_id="p07-example", aliases=["interferometer"], ingest_roots=[ {"source": "vault", "subpath": "incoming/projects/p07-example"} ], ) except ValueError as exc: assert "collisions" in str(exc) else: raise AssertionError("Expected collision to prevent project registration") finally: config.settings = original_settings