"""The plugin installs, packages and serves on a machine with no POSIX shell. Two adopters are waiting and neither is guaranteed to be on macOS, so "runs on Windows" is an acceptance criterion here rather than a nicety. The measurement that started this was narrow: `.mcp.json` named `bash` as the command, stock Windows has no bash, and the connector therefore could not start at all. Five shell entry points behind it reached for `grep`, `sed`, `find`, `awk`, `zip` and `unzip`, none of which are there either. The Python underneath was already clean -- a scan of `scripts/*.py` and `scripts/jobbsok_lib/` found no `/tmp`, no `/usr`, no `os.uname` and no home directory assumption -- so the port is of the shell layer only. What this file can and cannot measure, stated rather than implied: * **Measurable here.** Every platform-dependent decision is a pure function taking the platform as an argument -- `venv_python`, `path_candidates`, `venv_location` -- so the Windows branch is exercised on this Mac by passing `"nt"`. That is the difference between a Windows port and a Windows claim. * **Not measurable here.** Whether Claude Code and Cowork actually spawn the server on Windows, and whether Cowork on Windows bridges to a host-side stdio MCP the way it does on this Mac. `docs/cowork-probe.md` measured macOS only. Nothing in this file asserts either, in either direction. Style note: this file follows tests/test_public_surface.py. """ import ast import json import os import re import subprocess import sys import pytest REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SCRIPTS = os.path.join(REPO, "scripts") MCP = os.path.join(REPO, ".mcp.json") README = os.path.join(REPO, "README.md") #: The entry points an adopter has to be able to run. `cowork_probe_check` is #: not among them: it reads this Mac's own Claude logs and is a measurement #: tool, not part of installing, packaging or serving. INNGANGSPUNKTER = ("bootstrap.py", "build_stamp.py", "package_plugin.py", "jobbsok_tools_launch.py") #: Programs that do not exist on a stock Windows install. A `command` naming #: any of these is the bug this port closed. POSIX_SKALL = ("bash", "sh", "zsh", "dash", "/bin/bash", "/bin/sh", "env") def sporede_filer(): ut = subprocess.run(["git", "-C", REPO, "ls-files"], capture_output=True, text=True) assert ut.returncode == 0, ut.stderr return [p for p in ut.stdout.split("\n") if p] def test_the_mcp_server_is_started_without_a_posix_shell(): with open(MCP, "r", encoding="utf-8") as handle: konfig = json.load(handle) servere = konfig["mcpServers"] assert list(servere) == ["jobbsok-tools"], ( "the declared servers are %r; the name is pinned because the server " "echoes it back and test_mcp_jobbsok_tools.py compares them" % (list(servere),) ) server = servere["jobbsok-tools"] assert server["command"] not in POSIX_SKALL, ( "command is %r, which stock Windows does not have -- this is the hard " "blocker the port exists to remove" % (server["command"],) ) for arg in server.get("args", []): assert not arg.endswith(".sh"), ( "args still carry a shell script (%r); an interpreter that can run " "it is the same dependency wearing a different name" % (arg,) ) assert any("${CLAUDE_PLUGIN_ROOT}" in arg for arg in server.get("args", [])), ( "the server script must be addressed through ${CLAUDE_PLUGIN_ROOT}" ) def test_the_mcp_command_carries_a_default_so_it_never_expands_to_nothing(): """No single interpreter name exists on every platform, so the name is a variable -- and a variable without a default is worse than a wrong name. Measured against the CLI's expansion pass: `${VAR}` with VAR unset is passed through UNEXPANDED and merely warned about, so the spawn would try to run a program literally called `${VAR}`. `${VAR:-default}` is the only form that always yields a name. """ with open(MCP, "r", encoding="utf-8") as handle: server = json.load(handle)["mcpServers"]["jobbsok-tools"] treff = re.match(r"^\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]+)\}$", server["command"]) assert treff, ( "command is %r. It has to be ${VAR:-default}: no literal interpreter " "name exists on macOS and Windows both -- `python3` is absent on stock " "Windows, `python` and `py` are absent on this Mac (measured)." % (server["command"],) ) variabel, standard = treff.group(1), treff.group(2) assert variabel != "JOBBSOK_PYTHON", ( "the launch variable must not be JOBBSOK_PYTHON. That one names the " "interpreter to SERVE on, and the launcher refuses outright rather " "than falling through when it is set -- so a Windows adopter setting " "it merely to spell `python` would silently bypass the bootstrapped " "virtualenv and serve without the guard." ) assert standard == "python3", ( "the default is %r; it must stay python3 so macOS and Linux keep " "working with nothing set, exactly as they did before the port" % (standard,) ) def test_no_entry_point_is_a_shell_script(): skall = [p for p in sporede_filer() if p.startswith("scripts/") and p.endswith(".sh")] assert skall == [], ( "scripts/ still tracks shell entry points: %r. Installing, packaging " "and starting the server must not need a shell." % (skall,) ) for navn in INNGANGSPUNKTER: assert os.path.isfile(os.path.join(SCRIPTS, navn)), ( "entry point scripts/%s does not exist" % navn ) def test_the_entry_points_parse_on_the_interpreter_they_exist_to_refuse(): """A refusal that is a SyntaxError is not a refusal. The launcher and the bootstrap are both started by an interpreter they do not trust -- under an empty launchd PATH on this Mac that is 3.9.6 (risk H2) -- and both exist to say no to it in words. Syntax newer than the floor they enforce would turn the message into a traceback. """ for navn in ("jobbsok_tools_launch.py", "bootstrap.py"): with open(os.path.join(SCRIPTS, navn), "r", encoding="utf-8") as handle: kilde = handle.read() try: ast.parse(kilde, filename=navn, feature_version=(3, 7)) except SyntaxError as feil: pytest.fail( "scripts/%s does not parse on Python 3.7: %s (line %s). It has " "to refuse an old interpreter in words, not in a traceback." % (navn, feil.msg, feil.lineno) ) def test_the_launcher_looks_for_a_virtualenv_where_each_platform_puts_one(): import jobbsok_tools_launch as launcher assert launcher.venv_python("/x/venv", "posix") == os.path.join( "/x/venv", "bin", "python" ) # Windows venvs have Scripts\python.exe and no python3 at all, so a port # that kept `bin/python3` would find nothing on the one platform it was # written for. windows = launcher.venv_python("C:\\x\\venv", "nt") assert windows.endswith(os.path.join("Scripts", "python.exe")), windows assert "bin" not in windows.split(os.sep) def test_the_launcher_asks_for_the_interpreter_name_each_platform_uses(): import jobbsok_tools_launch as launcher assert launcher.path_candidates("posix") == ("python3",), ( "POSIX must keep exactly what the shell version tried; anything more " "is a behaviour change smuggled in under a port" ) assert launcher.path_candidates("nt") == ("python", "py"), ( "a bare python3 on Windows is the Microsoft Store stub, which opens a " "shop instead of running anything" ) def test_the_launcher_still_refuses_the_path_interpreter_that_fails_the_gate(): """The M9 property, as a unit rather than a subprocess. A mutation that reinstated an ungated PATH fallback is the failure this guards, and it has to hold on both platforms' interpreter names -- the subprocess test next door can only ever exercise the host's own. """ import jobbsok_tools_launch as launcher funnet = [] def which(navn): funnet.append(navn) return "/falsk/" + os.path.basename(navn) tolk = launcher.resolve_interpreter( {}, "/ingen/plugin/rot", which=which, gate=lambda _tolk: False ) assert tolk is None, ( "the launcher returned %r from a candidate that failed the version " "gate; every branch is gated or none of them are" % (tolk,) ) assert funnet, "the launcher never looked at a candidate at all" # And the other direction, so the test above cannot pass by never looking. tolk = launcher.resolve_interpreter( {}, "/ingen/plugin/rot", which=which, gate=lambda _tolk: True ) assert tolk is not None def test_an_interpreter_named_by_the_operator_is_refused_and_never_skipped(): import jobbsok_tools_launch as launcher with pytest.raises(launcher.Avvist) as fanget: launcher.resolve_interpreter( {"JOBBSOK_PYTHON": "/finnes/ikke"}, "/ingen/plugin/rot", which=lambda navn: None, gate=lambda _tolk: True, ) assert "not executable" in fanget.value.linjer[0] with pytest.raises(launcher.Avvist) as fanget: launcher.resolve_interpreter( {"JOBBSOK_PYTHON": "/finnes/men/er/gammel"}, "/ingen/plugin/rot", which=lambda navn: navn, gate=lambda _tolk: False, ) assert "3.10" in " ".join(fanget.value.linjer), fanget.value.linjer def test_the_bootstrap_builds_the_environment_where_a_plugin_update_cannot_erase_it(): import bootstrap katalog, art = bootstrap.venv_location({"CLAUDE_PLUGIN_DATA": "/data"}, "/repo") assert katalog == os.path.join("/data", "venv") assert "plugin-data" in art katalog, art = bootstrap.venv_location({}, "/repo") assert katalog == os.path.join("/repo", ".venv") assert "repo root" in art assert bootstrap.venv_python("/x/venv", "nt").endswith( os.path.join("Scripts", "python.exe") ) def test_the_bootstrap_refuses_an_interpreter_below_the_floor(): import bootstrap with pytest.raises(bootstrap.Avvist) as fanget: bootstrap.resolve_interpreter( {"JOBBSOK_PYTHON": "/finnes/ikke"}, which=lambda navn: None ) assert "no such interpreter" in fanget.value.linjer[0] with pytest.raises(bootstrap.Avvist) as fanget: bootstrap.resolve_interpreter( {"JOBBSOK_PYTHON": "/gammel"}, which=lambda navn: navn, gate=lambda _tolk: False, ) assert "3.10" in " ".join(fanget.value.linjer), fanget.value.linjer # And at the process boundary, where an adopter meets it: a named # interpreter that is not there must exit non-zero rather than building an # environment on something else. kjort = subprocess.run( [sys.executable, os.path.join(SCRIPTS, "bootstrap.py")], env=dict(os.environ, JOBBSOK_PYTHON="/finnes/ikke/heller"), capture_output=True, text=True, ) assert kjort.returncode != 0, kjort.stdout assert "JOBBSOK_PYTHON" in kjort.stderr def test_the_readme_install_block_carries_a_windows_route(): with open(README, "r", encoding="utf-8") as handle: tekst = handle.read() installer = tekst.split("## Install", 1)[1].split("\n## ", 1)[0] assert "Windows" in installer, "the install block never mentions Windows" for skall in ("WSL", "Git Bash"): assert skall not in installer, ( "the install block still routes Windows through %r. A POSIX shell " "as a prerequisite is precisely the gap this port closed." % skall ) def test_the_probe_checker_still_recognises_a_leaked_archive_entry(): """The fifth entry point, and the one piece of logic in it worth gating. `cowork_probe_check` measures this Mac and only this Mac -- what it reads lives under ~/Library -- so what the port had to preserve was not portability but the predicate that decides whether the probe vehicle shipped something local. """ import cowork_probe_check as probe for lekk in (".git/config", "STATE.md", ".venv/bin/python", ".claude/x.md"): assert probe.lekkasje(lekk), "%r would be shipped unnoticed" % lekk for greit in ("skills/probe-versjon/SKILL.md", ".claude-plugin/plugin.json"): assert not probe.lekkasje(greit), "%r is not a leak" % greit assert probe.antall_svar("- Host-MCP: ja\n- python3: nei\n") == 2 assert probe.antall_svar("- Host-MCP: kanskje\n") == 0