140 lines
5.2 KiB
Python
140 lines
5.2 KiB
Python
"""Tests for myagents.commands.sync_workspace."""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import click
|
|
import pytest
|
|
|
|
from myagents.commands import sync_workspace as sync_mod
|
|
|
|
|
|
@pytest.fixture()
|
|
def runtime(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
rt = tmp_path / "runtime"
|
|
(rt / ".agents" / "skills" / "demo").mkdir(parents=True)
|
|
(rt / ".agents" / "skills" / "demo" / "SKILL.md").write_text("demo v1")
|
|
(rt / "agents.md").write_text("agent rules v1")
|
|
(rt / "VERSION").write_text("v0.0.0-test\n")
|
|
monkeypatch.setattr(sync_mod, "get_runtime_root", lambda: rt)
|
|
return rt
|
|
|
|
|
|
@pytest.fixture()
|
|
def workspace(tmp_path: Path) -> Path:
|
|
ws = tmp_path / "workspace"
|
|
ws.mkdir()
|
|
return ws
|
|
|
|
|
|
def _read_stamp(ws: Path) -> dict:
|
|
return json.loads((ws / sync_mod.STAMP_NAME).read_text())
|
|
|
|
|
|
class TestSyncWorkspace:
|
|
def test_new_files_copied_and_symlinks_made(
|
|
self, runtime: Path, workspace: Path
|
|
) -> None:
|
|
report = sync_mod.sync_workspace(workspace)
|
|
skill = workspace / ".agents" / "skills" / "demo" / "SKILL.md"
|
|
assert skill.read_text() == "demo v1"
|
|
assert (workspace / "agents.md").read_text() == "agent rules v1"
|
|
assert (workspace / "CLAUDE.md").is_symlink()
|
|
assert (workspace / ".claude").is_symlink()
|
|
assert report["added"] and not report["skipped"]
|
|
assert _read_stamp(workspace)["version"] == "v0.0.0-test"
|
|
|
|
def test_second_run_is_noop(self, runtime: Path, workspace: Path) -> None:
|
|
sync_mod.sync_workspace(workspace)
|
|
report = sync_mod.sync_workspace(workspace)
|
|
assert report == {"added": [], "updated": [], "skipped": [], "removed": []}
|
|
|
|
def test_upgrade_overwrites_untouched_file(
|
|
self, runtime: Path, workspace: Path
|
|
) -> None:
|
|
sync_mod.sync_workspace(workspace)
|
|
(runtime / "agents.md").write_text("agent rules v2")
|
|
report = sync_mod.sync_workspace(workspace)
|
|
assert (workspace / "agents.md").read_text() == "agent rules v2"
|
|
assert "agents.md" in report["updated"]
|
|
|
|
def test_user_modified_file_is_skipped(
|
|
self, runtime: Path, workspace: Path
|
|
) -> None:
|
|
sync_mod.sync_workspace(workspace)
|
|
(workspace / "agents.md").write_text("my local edits")
|
|
(runtime / "agents.md").write_text("agent rules v2")
|
|
report = sync_mod.sync_workspace(workspace)
|
|
assert (workspace / "agents.md").read_text() == "my local edits"
|
|
assert "agents.md" in report["skipped"]
|
|
|
|
def test_removed_from_runtime_is_removed_from_workspace(
|
|
self, runtime: Path, workspace: Path
|
|
) -> None:
|
|
sync_mod.sync_workspace(workspace)
|
|
(runtime / ".agents" / "skills" / "demo" / "SKILL.md").unlink()
|
|
report = sync_mod.sync_workspace(workspace)
|
|
assert not (workspace / ".agents" / "skills" / "demo" / "SKILL.md").exists()
|
|
assert report["removed"]
|
|
|
|
def test_git_checkout_refused_without_force(
|
|
self, runtime: Path, workspace: Path
|
|
) -> None:
|
|
(workspace / ".git").mkdir()
|
|
with pytest.raises(click.ClickException):
|
|
sync_mod.sync_workspace(workspace)
|
|
report = sync_mod.sync_workspace(workspace, force=True)
|
|
assert report["added"]
|
|
|
|
|
|
class TestCreateLauncher:
|
|
@pytest.fixture()
|
|
def fake_home(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
home = tmp_path / "home"
|
|
(home / "Desktop").mkdir(parents=True)
|
|
monkeypatch.setattr(Path, "home", staticmethod(lambda: home))
|
|
return home
|
|
|
|
def test_macos_app_bundle_with_icon(
|
|
self, runtime: Path, fake_home: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
(runtime / "assets" / "icon").mkdir(parents=True)
|
|
(runtime / "assets" / "icon" / "XiaoheAgent.icns").write_bytes(b"icns")
|
|
legacy = fake_home / "Desktop" / "XiaoheAgent.command"
|
|
legacy.write_text("#!/bin/sh\n")
|
|
monkeypatch.setattr(
|
|
sync_mod.os, "uname", lambda: type("U", (), {"sysname": "Darwin"})
|
|
)
|
|
sync_mod._create_launcher()
|
|
app = fake_home / "Desktop" / "XiaoheAgent.app"
|
|
executable = app / "Contents" / "MacOS" / "XiaoheAgent"
|
|
assert (app / "Contents" / "Info.plist").is_file()
|
|
assert (app / "Contents" / "Resources" / "XiaoheAgent.icns").is_file()
|
|
assert executable.stat().st_mode & 0o111
|
|
assert "xiaohe" in executable.read_text()
|
|
assert not legacy.exists() # superseded .command removed
|
|
|
|
def test_linux_desktop_entry_with_icon(
|
|
self, runtime: Path, fake_home: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
(runtime / "assets" / "icon").mkdir(parents=True)
|
|
(runtime / "assets" / "icon" / "xiaohe-icon-512.png").write_bytes(b"png")
|
|
monkeypatch.setattr(
|
|
sync_mod.os, "uname", lambda: type("U", (), {"sysname": "Linux"})
|
|
)
|
|
sync_mod._create_launcher()
|
|
entry = (
|
|
fake_home / ".local" / "share" / "applications" / "XiaoheAgent.desktop"
|
|
)
|
|
assert "Icon=xiaohe-agent" in entry.read_text()
|
|
assert (
|
|
fake_home
|
|
/ ".local"
|
|
/ "share"
|
|
/ "icons"
|
|
/ "hicolor"
|
|
/ "512x512"
|
|
/ "apps"
|
|
/ "xiaohe-agent.png"
|
|
).is_file()
|