"""Tests for the catalog > bundled resolver.""" from __future__ import annotations import json from pathlib import Path import pytest def _manifest(name: str = "fileshare") -> dict: return { "name": name, "display_name": "Fileshare", "version": "0.1.0", "description": "x", "volumes": [], "ports": [], "icon": "icon.svg", } @pytest.fixture def sources_mod(tmp_path, monkeypatch): monkeypatch.setenv("FURTKA_CATALOG_DIR", str(tmp_path / "catalog")) monkeypatch.setenv("FURTKA_BUNDLED_APPS_DIR", str(tmp_path / "bundled")) import importlib from furtka import paths as p from furtka import sources as s importlib.reload(p) importlib.reload(s) return s def _seed_app(root: Path, name: str, manifest: dict | None = None) -> Path: folder = root / name folder.mkdir(parents=True) (folder / "manifest.json").write_text(json.dumps(manifest or _manifest(name))) return folder def test_resolve_app_name_returns_none_when_absent(sources_mod): assert sources_mod.resolve_app_name("nope") is None def test_resolve_app_name_prefers_catalog_over_bundled(sources_mod, tmp_path): _seed_app(tmp_path / "catalog" / "apps", "fileshare") _seed_app(tmp_path / "bundled", "fileshare") result = sources_mod.resolve_app_name("fileshare") assert result is not None assert result.origin == "catalog" assert result.path.parent.name == "apps" assert result.path.parent.parent.name == "catalog" def test_resolve_app_name_falls_back_to_bundled(sources_mod, tmp_path): _seed_app(tmp_path / "bundled", "fileshare") result = sources_mod.resolve_app_name("fileshare") assert result is not None assert result.origin == "bundled" def test_resolve_app_name_ignores_folder_without_manifest(sources_mod, tmp_path): # Empty folder is not a valid app even if the name matches. (tmp_path / "catalog" / "apps" / "fileshare").mkdir(parents=True) _seed_app(tmp_path / "bundled", "fileshare") result = sources_mod.resolve_app_name("fileshare") # Catalog entry without manifest is skipped; bundled wins. assert result.origin == "bundled" def test_list_available_unions_catalog_and_bundled(sources_mod, tmp_path): _seed_app(tmp_path / "catalog" / "apps", "fileshare") _seed_app(tmp_path / "bundled", "otherapp") names = {s.path.name: s.origin for s in sources_mod.list_available()} assert names == {"fileshare": "catalog", "otherapp": "bundled"} def test_list_available_catalog_wins_on_collision(sources_mod, tmp_path): _seed_app(tmp_path / "catalog" / "apps", "fileshare") _seed_app(tmp_path / "bundled", "fileshare") entries = sources_mod.list_available() assert len(entries) == 1 assert entries[0].origin == "catalog" def test_list_available_empty_when_neither_exists(sources_mod): assert sources_mod.list_available() == [] def test_list_available_skips_non_dirs_and_no_manifest(sources_mod, tmp_path): # A plain file in catalog/apps and an empty dir in bundled — both ignored. cat_root = tmp_path / "catalog" / "apps" cat_root.mkdir(parents=True) (cat_root / "not-a-dir.txt").write_text("x") (tmp_path / "bundled" / "emptyapp").mkdir(parents=True) _seed_app(tmp_path / "bundled", "realapp") entries = sources_mod.list_available() assert [e.path.name for e in entries] == ["realapp"]