import json from furtka.cli import main def _set_env(monkeypatch, tmp_path): monkeypatch.setenv("FURTKA_APPS_DIR", str(tmp_path)) def test_app_list_empty(tmp_path, monkeypatch, capsys): _set_env(monkeypatch, tmp_path) rc = main(["app", "list"]) assert rc == 0 assert "no apps installed" in capsys.readouterr().out def test_app_list_json_empty(tmp_path, monkeypatch, capsys): _set_env(monkeypatch, tmp_path) rc = main(["app", "list", "--json"]) assert rc == 0 assert json.loads(capsys.readouterr().out) == [] def test_app_list_json_with_one_app(tmp_path, monkeypatch, capsys): _set_env(monkeypatch, tmp_path) app = tmp_path / "fileshare" app.mkdir() (app / "manifest.json").write_text( json.dumps( { "name": "fileshare", "display_name": "Network Files", "version": "0.1.0", "description": "SMB", "description_long": "Long description here.", "volumes": ["files"], "ports": [445], "icon": "icon.svg", "open_url": "smb://{host}/files", "settings": [ { "name": "SMB_USER", "label": "User", "description": "SMB user", "type": "text", "default": "furtka", "required": True, } ], } ) ) rc = main(["app", "list", "--json"]) assert rc == 0 data = json.loads(capsys.readouterr().out) assert len(data) == 1 assert data[0]["ok"] is True m = data[0]["manifest"] assert m["name"] == "fileshare" assert m["description_long"] == "Long description here." assert m["open_url"] == "smb://{host}/files" assert len(m["settings"]) == 1 assert m["settings"][0]["name"] == "SMB_USER" assert m["settings"][0]["required"] is True assert m["settings"][0]["default"] == "furtka" def test_reconcile_dry_run_empty(tmp_path, monkeypatch, capsys): _set_env(monkeypatch, tmp_path) rc = main(["reconcile", "--dry-run"]) assert rc == 0 out = capsys.readouterr().out assert "0 actions" in out def test_app_install_bg_dispatches_to_runner(tmp_path, monkeypatch): """CLI `app install-bg ` must call install_runner.run_install(name). This is the entry point the HTTP API fires via systemd-run; regression here would leave the UI hanging at "pulling_image…" forever because the background never transitions state. """ _set_env(monkeypatch, tmp_path) from furtka import install_runner called = [] monkeypatch.setattr(install_runner, "run_install", lambda name: called.append(name)) rc = main(["app", "install-bg", "fileshare"]) assert rc == 0 assert called == ["fileshare"] def test_app_install_bg_returns_1_on_failure(tmp_path, monkeypatch, capsys): _set_env(monkeypatch, tmp_path) from furtka import install_runner def boom(name): raise RuntimeError("compose pull failed") monkeypatch.setattr(install_runner, "run_install", boom) rc = main(["app", "install-bg", "fileshare"]) assert rc == 1 err = capsys.readouterr().err assert "install-bg failed" in err assert "compose pull failed" in err