159 lines
5.6 KiB
Python
159 lines
5.6 KiB
Python
|
|
"""Tests for the ``--current-time`` fake-clock wiring.
|
||
|
|
|
||
|
|
The proxy runs every MCP service under the ``faketime`` wrapper when a
|
||
|
|
``--current-time`` value is supplied, so the service (and anything it spawns,
|
||
|
|
notably syntara's executeBash / executePython sandboxes) observes a clock
|
||
|
|
anchored to that instant and advancing at real wall-clock rate.
|
||
|
|
|
||
|
|
Unit tests here run anywhere — they exercise the pure mapping and the argv
|
||
|
|
assembly without launching a process. The integration test is skipped unless
|
||
|
|
the ``faketime`` wrapper is actually installed on PATH.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import shutil
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
from datetime import UTC, datetime
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from mcp_proxy.commands import mcp as mcp_cmd
|
||
|
|
from mcp_proxy.commands.mcp import resolve_faketime_launch, to_faketime_spec
|
||
|
|
from mcp_proxy.service import HookStep, _service_argv
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# to_faketime_spec — RFC3339/ISO-8601 -> faketime wrapper's absolute timestamp
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_utc_zulu():
|
||
|
|
assert to_faketime_spec("2025-01-15T09:00:00Z") == "2025-01-15 09:00:00"
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_naive_assumed_utc():
|
||
|
|
assert to_faketime_spec("2025-01-15T09:00:00") == "2025-01-15 09:00:00"
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_offset_converted_to_utc():
|
||
|
|
# 09:00 at +05:00 is 04:00 UTC.
|
||
|
|
assert to_faketime_spec("2025-01-15T09:00:00+05:00") == "2025-01-15 04:00:00"
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_negative_offset_crosses_midnight():
|
||
|
|
# 22:00 at -05:00 is 03:00 UTC the next day.
|
||
|
|
assert to_faketime_spec("2025-01-15T22:00:00-05:00") == "2025-01-16 03:00:00"
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_invalid_raises():
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
to_faketime_spec("not-a-timestamp")
|
||
|
|
|
||
|
|
|
||
|
|
def test_spec_empty_raises():
|
||
|
|
# An explicitly-passed empty --current-time is a bad value, not "no clock":
|
||
|
|
# run() routes it here (via `is not None`) so it fails fast rather than
|
||
|
|
# silently falling back to the real clock.
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
to_faketime_spec("")
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# _service_argv — prepend the faketime wrapper when a prefix is given
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
# ``python``/``python3`` resolve to the proxy's own interpreter (see
|
||
|
|
# service.resolve_command), so the argv carries ``sys.executable``, not the bare
|
||
|
|
# name. The faketime prefix, when present, wraps that resolved command.
|
||
|
|
def test_argv_no_prefix():
|
||
|
|
step = HookStep(command="python", args=["-m", "server", "--http"])
|
||
|
|
assert _service_argv(step, None) == [sys.executable, "-m", "server", "--http"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_argv_empty_prefix_is_noop():
|
||
|
|
step = HookStep(command="python", args=["-m", "server"])
|
||
|
|
assert _service_argv(step, []) == [sys.executable, "-m", "server"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_argv_with_faketime_prefix():
|
||
|
|
step = HookStep(command="python", args=["-m", "server"])
|
||
|
|
prefix = ["faketime", "2025-01-15 09:00:00"]
|
||
|
|
assert _service_argv(step, prefix) == [
|
||
|
|
"faketime",
|
||
|
|
"2025-01-15 09:00:00",
|
||
|
|
sys.executable,
|
||
|
|
"-m",
|
||
|
|
"server",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# resolve_faketime_launch — command prefix + env, with fail-fast
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_resolve_returns_prefix_and_utc_env(monkeypatch):
|
||
|
|
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: "/usr/bin/faketime")
|
||
|
|
prefix, env = resolve_faketime_launch("2025-01-15T09:00:00Z")
|
||
|
|
assert prefix == ["faketime", "2025-01-15 09:00:00"]
|
||
|
|
assert env == {"TZ": "UTC"}
|
||
|
|
|
||
|
|
|
||
|
|
def test_resolve_fails_fast_when_wrapper_missing(monkeypatch):
|
||
|
|
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: None)
|
||
|
|
with pytest.raises(SystemExit):
|
||
|
|
resolve_faketime_launch("2025-01-15T09:00:00Z")
|
||
|
|
|
||
|
|
|
||
|
|
def test_resolve_invalid_timestamp_fails(monkeypatch):
|
||
|
|
monkeypatch.setattr(mcp_cmd.shutil, "which", lambda _name: "/usr/bin/faketime")
|
||
|
|
with pytest.raises(SystemExit):
|
||
|
|
resolve_faketime_launch("not-a-timestamp")
|
||
|
|
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Integration — only when the faketime wrapper is installed
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
_HAS_FAKETIME = shutil.which("faketime") is not None
|
||
|
|
_SKIP_REASON = "faketime wrapper not installed on PATH"
|
||
|
|
|
||
|
|
# 2025-01-15T09:00:00Z
|
||
|
|
_FAKE_EPOCH = 1736931600
|
||
|
|
_SPEC = "2025-01-15 09:00:00"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.skipif(not _HAS_FAKETIME, reason=_SKIP_REASON)
|
||
|
|
def test_faketime_fakes_date():
|
||
|
|
out = subprocess.run(
|
||
|
|
["faketime", _SPEC, "date", "+%s"],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
env={"TZ": "UTC", "PATH": __import__("os").environ["PATH"]},
|
||
|
|
timeout=30,
|
||
|
|
)
|
||
|
|
assert out.returncode == 0, out.stderr
|
||
|
|
reported = int(out.stdout.strip())
|
||
|
|
# Clock advances from the anchor, so allow a small forward window.
|
||
|
|
assert _FAKE_EPOCH <= reported < _FAKE_EPOCH + 60
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.skipif(not _HAS_FAKETIME, reason=_SKIP_REASON)
|
||
|
|
def test_faketime_fakes_python():
|
||
|
|
out = subprocess.run(
|
||
|
|
["faketime", _SPEC, sys.executable, "-c", "import time; print(time.time())"],
|
||
|
|
capture_output=True,
|
||
|
|
text=True,
|
||
|
|
env={"TZ": "UTC", "PATH": __import__("os").environ["PATH"]},
|
||
|
|
timeout=30,
|
||
|
|
)
|
||
|
|
assert out.returncode == 0, out.stderr
|
||
|
|
reported = float(out.stdout.strip())
|
||
|
|
assert _FAKE_EPOCH <= reported < _FAKE_EPOCH + 60
|
||
|
|
|
||
|
|
|
||
|
|
def test_fake_epoch_matches_spec():
|
||
|
|
"""Guard the integration constants against drift."""
|
||
|
|
dt = datetime(2025, 1, 15, 9, 0, 0, tzinfo=UTC)
|
||
|
|
assert int(dt.timestamp()) == _FAKE_EPOCH
|