"""Opt-in packaging smoke: a real upstream rootfs must start this fork's MCP. Run in a disposable Linux namespace with NET_ADMIN and SYS_ADMIN: FIREGEX_TEST_INSTALLER=1 ./run_tests.sh standalone/test_installer_rootfs.py FIREGEX_STANDALONE_ARCHIVE may name a cached archive; its checksum is still checked. """ import asyncio import importlib.util import json import os from pathlib import Path import shutil import signal import subprocess import sys import time import pytest import requests from mcp import Client from mcp.client.streamable_http import streamable_http_client, create_mcp_http_client from helpers.net import free_port ROOT = Path(__file__).resolve().parents[2] @pytest.mark.root def test_standalone_runtime_exposes_fork_mcp(tmp_path, monkeypatch): if os.getenv("FIREGEX_TEST_INSTALLER") != "1": pytest.skip("Opt in with FIREGEX_TEST_INSTALLER=1 in a disposable namespace") monkeypatch.chdir(tmp_path) monkeypatch.setattr(sys, "argv", [str(ROOT / "run.py"), "status"]) spec = importlib.util.spec_from_file_location("standalone_installer", ROOT / "run.py") manager = importlib.util.module_from_spec(spec) spec.loader.exec_module(manager) os.chdir(tmp_path) manager.g.rootfs_path = str(tmp_path / "rootfs") manager.g.pid_file = str(tmp_path / "backend.pid") manager.g.configfile = str(tmp_path / "config.json") manager.g.standalone_mode = True port = free_port() manager.args = manager.gen_args(["start", "--standalone", "--host", "127.0.0.1", "--port", str(port), "--threads", "2", "--psw-on-web"]) cached = os.getenv("FIREGEX_STANDALONE_ARCHIVE") if cached: monkeypatch.setattr(manager, "download_file", lambda url, path: bool(shutil.copyfile(cached, path))) assert manager.setup_standalone_rootfs() rootfs = Path(manager.g.rootfs_path) pid = None try: assert manager.setup_standalone_mounts() manager.run_standalone() pid = manager.read_pid_file() address = f"http://127.0.0.1:{port}" for _ in range(60): try: if requests.get(address + "/api/status", timeout=1).status_code == 200: break except requests.RequestException: pass time.sleep(0.5) else: pytest.fail(Path("/tmp/app.log").read_text()[-8000:]) token = json.loads(subprocess.check_output([ "chroot", str(rootfs), "/bin/sh", "-c", "cd /execute && python3 -m mcp_api.tokens create --name installer-test --role operator --hours 1", ], text=True)) assert requests.post(address + "/mcp/", json={}, timeout=5).status_code == 401 async def scenario(): async with create_mcp_http_client(headers={"Authorization": "Bearer " + token["token"]}) as http: async with Client(streamable_http_client(address + "/mcp/", http_client=http)) as client: catalog = await client.list_tools() assert len(catalog.tools) == 28 overview = await client.call_tool("fgx_overview", {}) assert overview.structured_content["ok"], overview checked = await client.call_tool("fgx_python_check", {"code": "pass"}) assert checked.structured_content["data"]["ok"], checked asyncio.run(scenario()) assert (rootfs / "execute/.firegex-backend-deps").is_file() assert (rootfs / "execute/db/mcp/control.sqlite3").is_file() finally: pid = pid or manager.read_pid_file() if pid: try: os.killpg(pid, signal.SIGTERM) os.waitpid(pid, 0) except ProcessLookupError: pass manager.cleanup_standalone_mounts() manager.remove_pid_file()