109 lines
3.3 KiB
Python
109 lines
3.3 KiB
Python
|
|
"""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"]
|