Files
Artemis-Eapil/tests/unit/test_installation_update.py
T

159 lines
6.0 KiB
Python

"""Exercise upgrades against disposable Git repos and client configs, never the user's repo."""
import importlib.util
import json
from pathlib import Path
import shutil
import subprocess
import tomllib
import pytest
MODULE_PATH = Path(__file__).resolve().parents[2] / "scripts/update_installation.py"
spec = importlib.util.spec_from_file_location("installation_update", MODULE_PATH)
update = importlib.util.module_from_spec(spec)
spec.loader.exec_module(update)
def git(path, *args):
result = subprocess.run(
["git", "-C", str(path), *args], capture_output=True, text=True, timeout=15
)
assert result.returncode == 0, result.stderr
return result.stdout.strip()
@pytest.fixture
def repos(tmp_path):
if not shutil.which("git"):
pytest.skip("Git required")
remote = tmp_path / "upstream.git"
remote.mkdir()
git(remote, "init", "--bare")
author = tmp_path / "author"
author.mkdir()
git(author, "init", "-b", "main")
git(author, "config", "user.name", "Test Fixture")
git(author, "config", "user.email", "fixture@example.test")
(author / "source.txt").write_text("version1")
(author / ".gitignore").write_text(".env\n.venv/\n")
git(author, "add", "source.txt", ".gitignore")
git(author, "commit", "-m", "initial")
git(author, "remote", "add", "origin", str(remote))
git(author, "push", "-u", "origin", "main")
installed = tmp_path / "installed with spaces"
git(tmp_path, "clone", "-b", "main", str(remote), str(installed))
(installed / ".env").write_text("secret stays local")
(author / "source.txt").write_text("version2")
git(author, "add", "source.txt")
git(author, "commit", "-m", "upgrade")
git(author, "push")
return installed, author
def test_fast_forward_update_preserves_env_and_is_repeatable(repos):
installed, author = repos
assert update.update_source(installed, port=1) == 10
assert (installed / "source.txt").read_text() == "version2"
assert (installed / ".env").read_text() == "secret stays local"
assert git(installed, "rev-parse", "HEAD") == git(author, "rev-parse", "HEAD")
assert update.update_source(installed, port=1) == 0
def test_dirty_checkout_is_preserved(repos):
installed, _ = repos
before = git(installed, "rev-parse", "HEAD")
(installed / "source.txt").write_text("user edits")
assert update.update_source(installed, port=1) == 0
assert git(installed, "rev-parse", "HEAD") == before
assert (installed / "source.txt").read_text() == "user edits"
def test_diverged_checkout_is_not_merged(repos):
installed, _ = repos
git(installed, "config", "user.name", "Test Fixture")
git(installed, "config", "user.email", "fixture@example.test")
(installed / "local.txt").write_text("local commit")
git(installed, "add", "local.txt")
git(installed, "commit", "-m", "local")
before = git(installed, "rev-parse", "HEAD")
assert update.update_source(installed, port=1) == 0
assert git(installed, "rev-parse", "HEAD") == before
def test_zip_install_continues_without_git_mutation(tmp_path, capsys):
assert update.update_source(tmp_path, port=1) == 0
assert not (tmp_path / ".git").exists()
assert "Git" in capsys.readouterr().out
def test_codex_refresh_preserves_credentials_restrictions_and_other_settings(tmp_path):
root = tmp_path / "project"
path = tmp_path / "config.toml"
original = f"""model = "custom"
[mcp_servers.artemis]
command = "old-python"
cwd = {json.dumps(str(root))}
args = ["-m", "mcp_server"]
enabled = false
enabled_tools = [
"mobile_run_task",
]
disabled_tools = ["mobile_config"]
tool_timeout_sec = 900
[mcp_servers.artemis.env]
OPENAI_API_KEY = "test-only-secret"
[mcp_servers.other]
command = "leave-alone"
"""
path.write_text(original, encoding="utf-8")
assert update.refresh_codex(path, root, "new-python", ("mobile_run_task", "mobile_batch"))
data = tomllib.loads(path.read_text(encoding="utf-8"))
config = data["mcp_servers"]["artemis"]
assert config["enabled"] is False
assert config["disabled_tools"] == ["mobile_config"]
assert config["tool_timeout_sec"] == 900
assert config["env"]["OPENAI_API_KEY"] == "test-only-secret"
assert config["enabled_tools"] == ["mobile_run_task", "mobile_batch"]
assert data["mcp_servers"]["other"]["command"] == "leave-alone"
assert not update.refresh_codex(path, root, "new-python", ("mobile_run_task", "mobile_batch"))
assert path.with_name(path.name + ".artemis-update.bak").read_text(encoding="utf-8") == original
def test_json_only_updates_existing_matching_project(tmp_path):
path = tmp_path / "mcp.json"
root = tmp_path / "project"
config = {
"cwd": str(root),
"command": "old",
"args": ["-m", "mcp_server"],
"disabled": True,
"env": {"CUSTOM": "preserved"},
"autoApprove": [],
}
path.write_text(json.dumps({"mcpServers": {"artemis": config, "other": {"command": "keep"}}}))
assert not update.refresh_json(
path, ("mcpServers",), tmp_path / "different", "new", ("mobile_batch",)
)
assert update.refresh_json(path, ("mcpServers",), root, "new", ("mobile_batch",))
result = json.loads(path.read_text())["mcpServers"]
assert result["artemis"]["env"] == {"CUSTOM": "preserved"}
assert result["artemis"]["disabled"] is True
assert result["other"] == {"command": "keep"}
missing = tmp_path / "not-installed.json"
assert not update.refresh_json(missing, ("mcpServers",), root, "new", ())
assert not missing.exists()
def test_frontend_fingerprint_detects_lock_source_and_deleted_assets(tmp_path):
ui = tmp_path / "apps/showcase_ui"
(ui / "src").mkdir(parents=True)
(ui / "src/app.ts").write_text("one")
(ui / "package-lock.json").write_text("lock1")
first = update.frontend_fingerprint(tmp_path)
(ui / "package-lock.json").write_text("lock2")
second = update.frontend_fingerprint(tmp_path)
assert first != second
(ui / "src/app.ts").unlink()
assert update.frontend_fingerprint(tmp_path) != second