Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
"""End-to-end tests for lazy cryptography loading.
|
||||
|
||||
These tests invoke the real CLI paths as subprocesses to verify:
|
||||
1. `hermes secrets bitwarden setup --help` works (dispatch path)
|
||||
2. `hermes update --check` works (update path)
|
||||
3. `hermes secrets bitwarden disable` works (handler execution)
|
||||
4. `hermes secrets onepassword status` works (lazy backend loads on demand)
|
||||
|
||||
Unlike test_lazy_secrets_import.py (which inspects sys.modules), these
|
||||
run the actual commands and verify exit codes — the exact paths the
|
||||
reviewer flagged as unproven.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _run_hermes(args: list[str], timeout: int = 30) -> subprocess.CompletedProcess[str]:
|
||||
"""Run hermes CLI as a subprocess from repo root.
|
||||
|
||||
The child runs with all git remote URLs rewritten to an unreachable
|
||||
local path (GIT_CONFIG_* env overrides). These tests assert the
|
||||
lazy-crypto / no-self-lock invariants of the dispatch path, NOT update
|
||||
connectivity — but ``hermes update --check`` really does ``git fetch``
|
||||
against github.com when run bare. Under remote throttling that fetch
|
||||
can exceed the subprocess timeout and TimeoutExpired the test (exactly
|
||||
what happened on CI during the 2026-08-17 GitHub incident: both update
|
||||
tests red on main for hours with no code change). Rewriting the URLs
|
||||
makes the fetch fail instantly and deterministically; the update path
|
||||
still exercises its full parser/dispatch/fetch code and exits 1, which
|
||||
the assertions already accept.
|
||||
"""
|
||||
repo_root = Path(__file__).parent.parent
|
||||
env = dict(os.environ)
|
||||
env.update(
|
||||
{
|
||||
"GIT_CONFIG_COUNT": "2",
|
||||
# Rewrite every https:// and ssh remote to a nonexistent local
|
||||
# path so any fetch fails in milliseconds without touching the
|
||||
# network. insteadOf matching is prefix-based.
|
||||
"GIT_CONFIG_KEY_0": "url.file:///nonexistent-hermes-test-remote/.insteadOf",
|
||||
"GIT_CONFIG_VALUE_0": "https://",
|
||||
"GIT_CONFIG_KEY_1": "url.file:///nonexistent-hermes-test-remote/.insteadOf",
|
||||
"GIT_CONFIG_VALUE_1": "git@",
|
||||
}
|
||||
)
|
||||
return subprocess.run(
|
||||
[sys.executable, "-m", "hermes_cli.main"] + args,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(repo_root),
|
||||
env=env,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
|
||||
class TestSecretsDispatchE2E:
|
||||
"""End-to-end secrets dispatch — the path that must not self-lock."""
|
||||
|
||||
def test_bitwarden_setup_help(self) -> None:
|
||||
"""`hermes secrets bitwarden setup --help` must exit 0 and print usage.
|
||||
|
||||
This is the exact path that triggered the #86781 self-lock loop on
|
||||
Windows: setup/parser nested under lazy-loaded backend.
|
||||
"""
|
||||
result = _run_hermes(["secrets", "bitwarden", "setup", "--help"])
|
||||
assert result.returncode == 0, (
|
||||
f"bitwarden setup --help failed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
assert "usage" in result.stdout.lower()
|
||||
|
||||
def test_bitwarden_status(self) -> None:
|
||||
"""`hermes secrets bitwarden status` must exit 0 (runs lazy backend)."""
|
||||
result = _run_hermes(["secrets", "bitwarden", "status"])
|
||||
# status may return non-zero if not configured, but must NOT crash
|
||||
# with import errors, recursion, or missing subcommand
|
||||
assert result.returncode in (0, 1), (
|
||||
f"bitwarden status crashed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
# Must not contain import errors
|
||||
assert "ImportError" not in result.stderr
|
||||
assert "cannot import name" not in result.stderr
|
||||
|
||||
def test_bitwarden_disable(self) -> None:
|
||||
"""`hermes secrets bitwarden disable` must exit 0."""
|
||||
result = _run_hermes(["secrets", "bitwarden", "disable"])
|
||||
assert result.returncode == 0, (
|
||||
f"bitwarden disable failed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
|
||||
def test_onepassword_status(self) -> None:
|
||||
"""`hermes secrets onepassword status` must exit 0 (1Password lazy backend)."""
|
||||
result = _run_hermes(["secrets", "onepassword", "status"])
|
||||
assert result.returncode in (0, 1), (
|
||||
f"onepassword status crashed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
assert "ImportError" not in result.stderr
|
||||
|
||||
def test_onepassword_setup_help(self) -> None:
|
||||
"""`hermes secrets onepassword setup --help` must exit 0."""
|
||||
result = _run_hermes(["secrets", "onepassword", "setup", "--help"])
|
||||
assert result.returncode in (0, 2), (
|
||||
f"onepassword setup --help failed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
assert "ImportError" not in result.stderr
|
||||
|
||||
|
||||
class TestUpdatePathE2E:
|
||||
"""Update path — must not load cryptography.
|
||||
|
||||
These tests invoke the real `hermes update --check` path as a subprocess.
|
||||
The conftest.py live-system guard blocks this because the command string
|
||||
contains "update"; we bypass with the pytest mark.
|
||||
"""
|
||||
|
||||
@pytest.mark.live_system_guard_bypass
|
||||
def test_update_check_clean(self) -> None:
|
||||
"""`hermes update --check` must not load cryptography._rust."""
|
||||
result = _run_hermes(["update", "--check"])
|
||||
assert result.returncode in (0, 1, 2), (
|
||||
f"update --check crashed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
# No import errors
|
||||
assert "ImportError" not in result.stderr
|
||||
assert "cannot import name" not in result.stderr
|
||||
|
||||
@pytest.mark.live_system_guard_bypass
|
||||
def test_update_no_self_lock(self) -> None:
|
||||
"""Update path must not self-lock (cryptography._rust absent)."""
|
||||
result = _run_hermes(["update", "--check"])
|
||||
# The check itself may return non-zero (e.g. no updates), but
|
||||
# must not contain the self-lock defer message
|
||||
assert "deferred" not in result.stderr.lower()
|
||||
assert "self-lock" not in result.stderr.lower()
|
||||
assert "_rust.pyd" not in result.stderr.lower()
|
||||
|
||||
@pytest.mark.live_system_guard_bypass
|
||||
def test_main_update_check_crypto_absent_in_sys_modules(self) -> None:
|
||||
"""Decisive invariant: invoking main() with argv=['hermes','update','--check']
|
||||
leaves cryptography.hazmat.bindings._rust absent from sys.modules.
|
||||
|
||||
This is the exact invariant review flagged as unproven (#86782 review
|
||||
2026-08-15): an import-only test cannot observe lazy failures, because
|
||||
parser construction happens inside main(). Run main() itself in a
|
||||
subprocess, let it execute the update path, then assert sys.modules.
|
||||
|
||||
The check must run before _dispatch_update calls its (potentially
|
||||
lazy) network layer, so we instrument sys.modules immediately after
|
||||
parse_args() and before dispatch returns, using a monkeypatched
|
||||
_cmd_update_check that captures state then short-circuits.
|
||||
"""
|
||||
script = """
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
crypto_seen_at_dispatch = []
|
||||
|
||||
def capture_update_check(*args, **kwargs):
|
||||
# Run just before the real handler would; record crypto state.
|
||||
crypto_seen_at_dispatch.append(
|
||||
'cryptography.hazmat.bindings._rust' in sys.modules
|
||||
)
|
||||
# Short-circuit: don't actually call the network in tests.
|
||||
return 0
|
||||
|
||||
sys.argv = ['hermes', 'update', '--check']
|
||||
|
||||
import hermes_cli.main as m
|
||||
|
||||
# Patch the update handler so main() exercises its parser + dispatch
|
||||
# without doing network I/O. cmd_update (in main.py) calls
|
||||
# _self()._cmd_update_check(branch=..., branch_explicit=...) where _self()
|
||||
# resolves the hermes_cli.main module's lazily re-exported attribute —
|
||||
# so the patch must land on hermes_cli.main._cmd_update_check.
|
||||
with patch('hermes_cli.main._cmd_update_check', capture_update_check):
|
||||
try:
|
||||
m.main()
|
||||
except SystemExit as e:
|
||||
# argparse may sys.exit for --help / bad args; ignore for this probe
|
||||
if e.code not in (0, None):
|
||||
print(f'FAIL: main() exited with code {e.code}')
|
||||
sys.exit(1)
|
||||
|
||||
# 1. main() must have dispatched into our capture hook
|
||||
if not crypto_seen_at_dispatch:
|
||||
print('FAIL: update --check did not dispatch to _cmd_update_check')
|
||||
sys.exit(1)
|
||||
|
||||
# 2. At dispatch time, crypto must NOT be loaded
|
||||
if crypto_seen_at_dispatch[0]:
|
||||
print('FAIL: cryptography._rust loaded by main() before update dispatch')
|
||||
sys.exit(1)
|
||||
|
||||
# 3. After main() returned, crypto must STILL not be loaded
|
||||
if 'cryptography.hazmat.bindings._rust' in sys.modules:
|
||||
print('FAIL: cryptography._rust present in sys.modules after main()')
|
||||
sys.exit(1)
|
||||
|
||||
print('PASS: main() update --check path never loaded cryptography._rust')
|
||||
sys.exit(0)
|
||||
"""
|
||||
repo_root = Path(__file__).parent.parent
|
||||
probe = repo_root / "_test_main_update_crypto_probe.py"
|
||||
probe.write_text(script)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, probe.name],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
cwd=str(repo_root),
|
||||
timeout=60,
|
||||
)
|
||||
assert result.returncode == 0, (
|
||||
f"Decisive main()-level probe failed:\n"
|
||||
f"stdout: {result.stdout}\n"
|
||||
f"stderr: {result.stderr}"
|
||||
)
|
||||
assert "PASS" in result.stdout
|
||||
finally:
|
||||
probe.unlink(missing_ok=True)
|
||||
Reference in New Issue
Block a user