feat: 优化运行时判定
This commit is contained in:
176
tests/test_cli.py
Normal file
176
tests/test_cli.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""Tests for ``laya_router.cli`` PATH / symlink helpers.
|
||||
|
||||
These cover the path exposure logic added so ``laya-router`` and
|
||||
``laya-hook`` work from any shell without manual ``PATH`` setup.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from laya_router.cli import (
|
||||
_apply_path_symlinks,
|
||||
_bin_link_targets,
|
||||
_remove_path_symlinks,
|
||||
_user_path_bin_dir,
|
||||
ensure_path_symlinks,
|
||||
remove_path_symlinks,
|
||||
)
|
||||
|
||||
|
||||
class _FakeSettings:
|
||||
"""Minimal stand-in for ``Settings`` carrying just the paths we touch."""
|
||||
|
||||
def __init__(self, router: Path, hook: Path):
|
||||
self.router_bin = router
|
||||
self.hook_bin = hook
|
||||
self.mcp_bin = router.with_name("laya-mcp")
|
||||
|
||||
|
||||
def _make_fake_binaries(tmp_path: Path) -> tuple[Path, Path, _FakeSettings]:
|
||||
"""Build two venv-style binaries on disk and return (router, hook, settings)."""
|
||||
src_dir = tmp_path / "venv_bin"
|
||||
src_dir.mkdir()
|
||||
router = src_dir / "laya-router"
|
||||
hook = src_dir / "laya-hook"
|
||||
router.write_text("#!/bin/sh\necho router\n")
|
||||
hook.write_text("#!/bin/sh\necho hook\n")
|
||||
return router, hook, _FakeSettings(router, hook)
|
||||
|
||||
|
||||
def test_bin_link_targets_returns_both_binaries():
|
||||
from laya_router.config import load_settings
|
||||
|
||||
targets = _bin_link_targets(load_settings())
|
||||
names = [name for name, _ in targets]
|
||||
assert names == ["laya-router", "laya-hook", "laya-mcp"]
|
||||
assert targets[0][1].is_file()
|
||||
assert targets[1][1].is_file()
|
||||
|
||||
|
||||
def test_user_path_bin_dir_prefers_local_bin(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("PATH", f"{tmp_path}:/usr/bin")
|
||||
preferred = Path.home() / ".local" / "bin"
|
||||
if preferred.is_dir() and os.access(preferred, os.W_OK):
|
||||
assert _user_path_bin_dir() == preferred
|
||||
else:
|
||||
assert _user_path_bin_dir() == tmp_path
|
||||
|
||||
|
||||
def test_user_path_bin_dir_skips_system_dirs(monkeypatch):
|
||||
# Force "no writable dir exists" by overriding to a non-existent path.
|
||||
monkeypatch.setenv("LAYA_ROUTER_BIN_DIR", "/this/path/does/not/exist")
|
||||
monkeypatch.setenv("PATH", "/usr/local/bin:/usr/bin:/bin:/sbin")
|
||||
assert _user_path_bin_dir() is None
|
||||
|
||||
|
||||
def test_user_path_bin_dir_respects_env_override(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("LAYA_ROUTER_BIN_DIR", str(tmp_path))
|
||||
assert _user_path_bin_dir() == tmp_path
|
||||
|
||||
|
||||
def test_user_path_bin_dir_env_override_rejects_unwritable(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("LAYA_ROUTER_BIN_DIR", "/this/path/does/not/exist")
|
||||
assert _user_path_bin_dir() is None
|
||||
|
||||
|
||||
def test_apply_path_symlinks_creates_links(tmp_path):
|
||||
router, hook, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
|
||||
assert (bin_dir / "laya-router").is_symlink()
|
||||
assert (bin_dir / "laya-router").resolve() == router.resolve()
|
||||
assert (bin_dir / "laya-hook").is_symlink()
|
||||
assert (bin_dir / "laya-hook").resolve() == hook.resolve()
|
||||
|
||||
|
||||
def test_apply_path_symlinks_is_noop_when_correct(tmp_path):
|
||||
_, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
first = (bin_dir / "laya-router").lstat()
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
second = (bin_dir / "laya-router").lstat()
|
||||
|
||||
# Re-running on a correct symlink must not rewrite it.
|
||||
assert first.st_mtime_ns == second.st_mtime_ns
|
||||
|
||||
|
||||
def test_apply_path_symlinks_repairs_wrong_target(tmp_path):
|
||||
router, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
(bin_dir / "laya-router").symlink_to("/etc/passwd")
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
assert (bin_dir / "laya-router").resolve() == router.resolve()
|
||||
|
||||
|
||||
def test_apply_path_symlinks_repairs_broken_symlink(tmp_path):
|
||||
router, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
(bin_dir / "laya-router").symlink_to("/nonexistent/path")
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
assert (bin_dir / "laya-router").resolve() == router.resolve()
|
||||
|
||||
|
||||
def test_apply_path_symlinks_skips_unrelated_file(tmp_path, capsys):
|
||||
router, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
existing = bin_dir / "laya-router"
|
||||
existing.write_text("user's binary")
|
||||
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
|
||||
captured = capsys.readouterr()
|
||||
assert "已被占用" in captured.err
|
||||
# The user's file must not be clobbered.
|
||||
assert existing.read_text() == "user's binary"
|
||||
# And the other one should still get a fresh symlink.
|
||||
assert (bin_dir / "laya-hook").is_symlink()
|
||||
assert (bin_dir / "laya-router").resolve() != router.resolve()
|
||||
|
||||
|
||||
def test_remove_path_symlinks_removes_both(tmp_path):
|
||||
_, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
_apply_path_symlinks(settings, bin_dir)
|
||||
assert (bin_dir / "laya-router").is_symlink()
|
||||
assert (bin_dir / "laya-hook").is_symlink()
|
||||
|
||||
_remove_path_symlinks(settings, bin_dir)
|
||||
assert not (bin_dir / "laya-router").exists()
|
||||
assert not (bin_dir / "laya-hook").exists()
|
||||
|
||||
|
||||
def test_remove_path_symlinks_idempotent(tmp_path):
|
||||
_, _, settings = _make_fake_binaries(tmp_path)
|
||||
bin_dir = tmp_path / "bin"
|
||||
bin_dir.mkdir()
|
||||
|
||||
# No symlinks created — remove should still be safe.
|
||||
_remove_path_symlinks(settings, bin_dir)
|
||||
_remove_path_symlinks(settings, bin_dir)
|
||||
|
||||
|
||||
def test_public_helpers_warn_when_no_writable_path(monkeypatch, capsys):
|
||||
monkeypatch.setenv("LAYA_ROUTER_BIN_DIR", "/this/path/does/not/exist")
|
||||
settings = _FakeSettings(Path("/nonexistent/router"), Path("/nonexistent/hook"))
|
||||
|
||||
ensure_path_symlinks(settings)
|
||||
captured = capsys.readouterr()
|
||||
assert "没有可写的目录" in captured.err
|
||||
|
||||
# remove_path_symlinks is silent in the same situation.
|
||||
remove_path_symlinks(settings)
|
||||
Reference in New Issue
Block a user