"""Installation must select the fork, preserve data and propagate failures.""" import importlib.util import json import os from pathlib import Path import subprocess import sys from types import SimpleNamespace import pytest ROOT = Path(__file__).resolve().parents[2] def executable(path, source): path.write_text(f"#!{sys.executable}\n" + source) path.chmod(0o755) @pytest.fixture def installer_env(tmp_path): binaries = tmp_path / "bin" binaries.mkdir() executable(binaries / "git", """ import os,sys from pathlib import Path assert 'https://github.com/Av0idD4rk/firegex.git' in sys.argv if os.environ.get('CLONE_FAIL'): sys.exit(17) target=Path(sys.argv[-1]); target.mkdir() (target/'run.py').write_text('import os,sys,json; from pathlib import Path; ' 'Path(os.environ["RESULT"]).write_text(json.dumps(sys.argv[1:])); ' 'sys.exit(int(os.environ.get("RUN_EXIT", "0")))') """) executable(binaries / "docker", """ import os,sys info=os.environ.get('DOCKER_INFO','') print(info) sys.exit(0 if info else 1) """) executable(binaries / "id", "import os; print(os.environ['TEST_UID'])") executable(binaries / "sudo", """ import os,sys from pathlib import Path Path(os.environ['SUDO_USED']).touch() os.execvp(sys.argv[1],sys.argv[1:]) """) env = {**os.environ, "PATH": str(binaries) + os.pathsep + os.environ["PATH"], "FIREGEX_INSTALL_DIR": str(tmp_path / "installation with spaces"), "RESULT": str(tmp_path / "result.json"), "SUDO_USED": str(tmp_path / "sudo"), "TEST_UID": "1000", "DOCKER_INFO": '["name=seccomp"]'} return env @pytest.mark.parametrize("info,uid,arguments,sudo,forced", [ ('["name=seccomp"]', "1000", [], False, False), ('["name=seccomp"]', "1000", ["--standalone"], True, True), ("", "1000", [], True, False), ('["name=rootless"]', "1000", [], True, True), ("", "0", [], False, False), ]) def test_bootstrap_preserves_arguments_and_selects_privileges(installer_env, info, uid, arguments, sudo, forced): env = {**installer_env, "DOCKER_INFO": info, "TEST_UID": uid} literal = "EXAMPLE=spaces; $(not-a-command)" result = subprocess.run(["sh", str(ROOT / "firegex.sh"), *arguments, "--env", literal], env=env, capture_output=True, text=True) assert result.returncode == 0, result.stderr received = json.loads(Path(env["RESULT"]).read_text()) assert received[0] == "start" and literal in received assert ("--standalone" in received) == forced assert Path(env["SUDO_USED"]).exists() == sudo def test_existing_installation_is_untouched(installer_env): target = Path(installer_env["FIREGEX_INSTALL_DIR"]) target.mkdir() (target / "important.db").write_bytes(b"keep me") result = subprocess.run(["sh", str(ROOT / "firegex.sh")], env=installer_env, capture_output=True) assert result.returncode != 0 and b"already exists" in result.stderr assert (target / "important.db").read_bytes() == b"keep me" assert not Path(installer_env["RESULT"]).exists() @pytest.mark.parametrize("extra,arguments,code", [ ({"CLONE_FAIL": "1"}, [], 17), ({"RUN_EXIT": "42"}, [], 42), ({}, ["--prebuilt"], 1), ]) def test_installer_failure_is_reported(installer_env, extra, arguments, code): result = subprocess.run(["sh", str(ROOT / "firegex.sh"), *arguments], env={**installer_env, **extra}, capture_output=True) assert result.returncode == code @pytest.fixture def manager(tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) monkeypatch.setattr(sys, "argv", [str(ROOT / "run.py"), "status"]) spec = importlib.util.spec_from_file_location("installer_manager", ROOT / "run.py") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) os.chdir(tmp_path) source = tmp_path / "source" (source / "backend/mcp_api").mkdir(parents=True) (source / "backend/mcp_api/server.py").write_text("# new MCP backend\n") (source / "backend/pyproject.toml").write_text('[project]\ndependencies=["mcp==2.3.0"]\n') module.__file__ = str(source / "run.py") module.g.rootfs_path = str(tmp_path / "rootfs") monkeypatch.setattr(module, "cleanup_standalone_mounts", lambda: None) return module def make_runtime(manager): rootfs = Path(manager.g.rootfs_path) for folder in ("execute/db/mcp", "execute/modules", "etc"): (rootfs / folder).mkdir(parents=True, exist_ok=True) (rootfs / "execute/app.py").write_text("# existing backend\n") (rootfs / ".firegex-native-base").write_text(manager.STANDALONE_BASE_RELEASE) return rootfs def test_backend_install_preserves_databases_and_native_engine(manager, monkeypatch): rootfs = make_runtime(manager) db = rootfs / "execute/db/mcp/control.sqlite3" engine = rootfs / "execute/modules/fgex-proxy" db.write_bytes(b"credentials and changes") engine.write_bytes(b"native engine") source = Path(manager.__file__).parent / "backend" (source / "db").mkdir() (source / "db/unrelated.py").write_text("must not be copied") calls = [] monkeypatch.setattr(manager.subprocess, "run", lambda *a, **kw: calls.append(a) or SimpleNamespace(returncode=0)) assert manager.prepare_standalone_backend() assert (rootfs / "execute/mcp_api/server.py").read_text() == "# new MCP backend\n" assert db.read_bytes() == b"credentials and changes" and engine.read_bytes() == b"native engine" assert not (rootfs / "execute/db/unrelated.py").exists() assert len(calls) == 2 assert manager.prepare_standalone_backend() assert len(calls) == 2 # unchanged requirements do not need a network install def test_incompatible_runtime_is_refused_before_copy(manager): rootfs = make_runtime(manager) (rootfs / ".firegex-native-base").write_text("4.0.0") assert not manager.prepare_standalone_backend() assert not (rootfs / "execute/mcp_api").exists() def test_failed_dependencies_are_not_marked_installed(manager, monkeypatch): rootfs = make_runtime(manager) def fail(*a, **kw): raise subprocess.CalledProcessError(1, "pip") monkeypatch.setattr(manager.subprocess, "run", fail) assert not manager.prepare_standalone_backend() assert not (rootfs / "execute/.firegex-backend-deps").exists() @pytest.mark.parametrize("download_ok", [False, True]) def test_failed_download_or_checksum_leaves_no_rootfs(manager, monkeypatch, download_ok): monkeypatch.setattr(manager, "get_architecture", lambda: "amd64") def download(url, filename): assert "/5.0.1/" in url Path(filename).write_bytes(b"truncated or corrupt archive") return download_ok monkeypatch.setattr(manager, "download_file", download) assert not manager.setup_standalone_rootfs() assert not Path(manager.g.rootfs_path).exists() assert not list(Path(manager.g.rootfs_path).parent.glob(".firegex-download-*")) def test_partial_old_runtime_is_not_treated_as_installed(manager): Path(manager.g.rootfs_path).mkdir() assert not manager.setup_standalone_rootfs() @pytest.mark.parametrize("result,code", [(SimpleNamespace(returncode=17), 17), (None, 1)]) def test_docker_build_failure_reaches_the_installer(manager, monkeypatch, result, code): manager.args = manager.gen_args(["start", "--psw-on-web"]) manager.g.build = True for name, value in {"should_use_standalone": False, "is_linux": True, "nfqueue_exists": True, "check_already_running": False}.items(): monkeypatch.setattr(manager, name, lambda value=value: value) monkeypatch.setattr(manager, "cmd_check", lambda *a, **kw: True) monkeypatch.setattr(manager, "write_compose", lambda *a, **kw: None) monkeypatch.setattr(manager, "composecmd", lambda *a, **kw: result) with pytest.raises(SystemExit) as error: manager.main() assert error.value.code == code