| 1 | #!/usr/bin/env python3 |
| 2 | """Check downloaded wrappers without calling the model or changing client settings.""" |
| 3 | import json |
| 4 | import os |
| 5 | import pty |
| 6 | import select |
| 7 | import time |
| 8 | from pathlib import Path |
| 9 | import subprocess |
| 10 | import sys |
| 11 | import tempfile |
| 12 | |
| 13 | source = Path(sys.argv[1]).resolve() |
| 14 | with tempfile.TemporaryDirectory(prefix="snow-wrapper-test-") as temporary: |
| 15 | root = Path(temporary) |
| 16 | for name in ("codex", "claude"): |
| 17 | binary = root / name |
| 18 | binary.write_text('''#!/usr/bin/env python3 |
| 19 | import json, os, pathlib, sys |
| 20 | catalog = next((a.split("=",1)[1].strip('"') for a in sys.argv if a.startswith("model_catalog_json=")), None) |
| 21 | print(json.dumps({"args":sys.argv[1:],"env":{k:v for k,v in os.environ.items() if k.startswith(("SNOW", "ANTHROPIC", "CLAUDE", "API_", "ENABLE_CLAUDE"))},"catalog":json.loads(pathlib.Path(catalog).read_text()) if catalog else None})) |
| 22 | ''') |
| 23 | binary.chmod(0o755) |
| 24 | env = dict(os.environ, PATH=str(root) + os.pathsep + os.environ["PATH"], XDG_CONFIG_HOME=str(root / "config")) |
| 25 | env.pop("SNOWGLOBE_AI_KEY", None) |
| 26 | env.pop("SNOWGLOBE_AI_URL", None) |
| 27 | for name in ("snow-codex", "snow-claude"): |
| 28 | wrapper = source / name |
| 29 | subprocess.run(["bash", "-n", wrapper], check=True) |
| 30 | failed = subprocess.run(["bash", wrapper], env=env, stdin=subprocess.DEVNULL, capture_output=True) |
| 31 | assert failed.returncode == 1 and b"SNOWGLOBE_AI_KEY" in failed.stderr |
| 32 | key = root / "config/snowglobe-ai/api-key" |
| 33 | key.parent.mkdir(parents=True, exist_ok=True) |
| 34 | master, slave = pty.openpty() |
| 35 | process = subprocess.Popen(["bash", wrapper, "--help"], env=env, stdin=slave, stdout=subprocess.PIPE, stderr=slave) |
| 36 | os.close(slave) |
| 37 | prompt = b"" |
| 38 | deadline = time.monotonic() + 10 |
| 39 | try: |
| 40 | while b"Snow Globe API key:" not in prompt: |
| 41 | assert time.monotonic() < deadline, "first-run key prompt did not appear" |
| 42 | if select.select([master], [], [], 1)[0]: |
| 43 | prompt += os.read(master, 4096) |
| 44 | os.write(master, b"wrapper-canary\n") |
| 45 | output, _ = process.communicate(timeout=10) |
| 46 | assert process.returncode == 0 and json.loads(output)["env"]["SNOWGLOBE_AI_KEY"] == "wrapper-canary" |
| 47 | assert key.read_text() == "wrapper-canary\n" and key.stat().st_mode & 0o777 == 0o600 |
| 48 | finally: |
| 49 | if process.poll() is None: process.kill() |
| 50 | os.close(master) |
| 51 | data = json.loads(subprocess.check_output(["bash", wrapper, "--help", "argument with spaces"], env={**env, "ANTHROPIC_AUTH_TOKEN":"old-auth", "CLAUDE_CODE_OAUTH_TOKEN":"old-login"})) |
| 52 | assert data["args"][-2:] == ["--help", "argument with spaces"] |
| 53 | assert "wrapper-canary" not in " ".join(data["args"]) |
| 54 | assert data["env"]["SNOWGLOBE_AI_KEY"] == "wrapper-canary" |
| 55 | assert key.stat().st_mode & 0o777 == 0o600 |
| 56 | if name == "snow-codex": |
| 57 | assert data["args"][:2] == ["--no-daemon", "--approve-for-me"] |
| 58 | assert data["catalog"]["models"][0]["context_window"] == 131072 |
| 59 | assert not list(key.parent.glob("catalog.*")), "temporary catalog leaked" |
| 60 | else: |
| 61 | assert data["args"][:2] == ["--permission-mode", "auto"] |
| 62 | assert "ANTHROPIC_AUTH_TOKEN" not in data["env"] and "CLAUDE_CODE_OAUTH_TOKEN" not in data["env"] |
| 63 | assert data["env"]["CLAUDE_CODE_AUTO_MODE_SERVER"] == "0" |
| 64 | assert data["env"]["API_TIMEOUT_MS"] == "28800000" |
| 65 | credentials = Path(data["env"]["CLAUDE_SECURESTORAGE_CONFIG_DIR"]) |
| 66 | assert credentials.stat().st_mode & 0o777 == 0o700 |
| 67 | key.unlink() |
| 68 | print("Both downloaded wrappers preserve arguments, enable auto approval, isolate credentials, and clean up.") |