Files
tracer-cloud--opensre/tests/interactive_shell/runtime/test_first_launch_github.py
T
wehub-resource-sync 4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

282 lines
10 KiB
Python

from __future__ import annotations
import io
import pytest
from rich.console import Console
from integrations.github import login as github_login_mod
from integrations.github.login import GitHubLoginResult
from integrations.github.mcp import DEFAULT_GITHUB_MCP_TOOLSETS, DEFAULT_GITHUB_MCP_URL
from integrations.github.mcp_oauth import GitHubDeviceCode
from platform.analytics import source as analytics_source
from platform.terminal import theme as ui_theme
from surfaces.interactive_shell.runtime.startup import first_launch_github as flg
def _console() -> Console:
return Console(file=io.StringIO(), force_terminal=False, highlight=False)
def _terminal_console(output: io.StringIO) -> Console:
return Console(file=output, force_terminal=True, color_system="truecolor", highlight=False)
def _force_required(monkeypatch: pytest.MonkeyPatch) -> None:
"""Set every gate input so that login would be required."""
monkeypatch.delenv("OPENSRE_SKIP_GITHUB_LOGIN", raising=False)
monkeypatch.setattr(flg, "is_test_run", lambda: False)
monkeypatch.setattr(flg, "repl_tty_interactive", lambda: True)
monkeypatch.setattr(flg, "_github_already_configured", lambda: False)
monkeypatch.setattr(flg, "read_github_login_deferred", lambda: False)
def test_gate_required_when_all_conditions_met(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
assert flg.should_require_github_login() is True
@pytest.mark.parametrize("value", ["1", "true", "YES", "on"])
def test_gate_skipped_by_env(monkeypatch: pytest.MonkeyPatch, value: str) -> None:
_force_required(monkeypatch)
monkeypatch.setenv("OPENSRE_SKIP_GITHUB_LOGIN", value)
assert flg.should_require_github_login() is False
def test_gate_skipped_in_test_run(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
monkeypatch.setattr(flg, "is_test_run", lambda: True)
assert flg.should_require_github_login() is False
def test_gate_required_on_linux_when_interactive(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
assert flg.should_require_github_login() is True
def test_gate_skipped_when_not_tty(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
monkeypatch.setattr(flg, "repl_tty_interactive", lambda: False)
assert flg.should_require_github_login() is False
def test_gate_skipped_when_github_already_configured(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
monkeypatch.setattr(flg, "_github_already_configured", lambda: True)
assert flg.should_require_github_login() is False
def test_gate_required_when_github_not_configured(monkeypatch: pytest.MonkeyPatch) -> None:
"""Regression: GitHub config is authoritative. A prior completed login (no
longer recorded via any standalone marker) must not let the REPL start once
the GitHub integration has been removed."""
_force_required(monkeypatch)
monkeypatch.setattr(flg, "_github_already_configured", lambda: False)
assert flg.should_require_github_login() is True
@pytest.mark.parametrize(
"ci_env",
[
("CI", "true"),
("GITHUB_ACTIONS", "true"),
("OPENSRE_IS_TEST", "1"),
],
)
def test_gate_skipped_in_ci_like_environment(
monkeypatch: pytest.MonkeyPatch, ci_env: tuple[str, str]
) -> None:
monkeypatch.delenv("OPENSRE_SKIP_GITHUB_LOGIN", raising=False)
monkeypatch.delenv("CI", raising=False)
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
monkeypatch.delenv("OPENSRE_IS_TEST", raising=False)
monkeypatch.delenv("OPENSRE_INVESTIGATION_SOURCE", raising=False)
monkeypatch.delenv("PYTEST_CURRENT_TEST", raising=False)
monkeypatch.setattr(flg, "is_test_run", analytics_source.is_test_run)
monkeypatch.setattr(flg, "repl_tty_interactive", lambda: True)
monkeypatch.setattr(flg, "_github_already_configured", lambda: False)
monkeypatch.setenv(ci_env[0], ci_env[1])
assert flg.should_require_github_login() is False
def test_gate_skipped_when_github_login_deferred(monkeypatch: pytest.MonkeyPatch) -> None:
_force_required(monkeypatch)
monkeypatch.setattr(flg, "read_github_login_deferred", lambda: True)
assert flg.should_require_github_login() is False
def test_gate_required_when_stale_github_store_record_has_no_token(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Legacy installs may have an abandoned github store row without credentials."""
_force_required(monkeypatch)
monkeypatch.setattr(
"integrations.store.get_integration",
lambda service: (
{
"credentials": {
"mode": "streamable-http",
"url": DEFAULT_GITHUB_MCP_URL,
"toolsets": list(DEFAULT_GITHUB_MCP_TOOLSETS),
}
}
if service == "github"
else None
),
)
assert flg.should_require_github_login() is True
def test_device_code_prompt_highlights_user_code(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("NO_COLOR", raising=False)
monkeypatch.delenv("FORCE_COLOR", raising=False)
ui_theme.set_active_theme("blue")
output = io.StringIO()
code = GitHubDeviceCode(
device_code="dev-123",
user_code="WXYZ-1234",
verification_uri="https://github.com/login/device",
expires_in=900,
interval=5,
)
flg._show_device_code(_terminal_console(output), code)
rendered = output.getvalue()
assert f"{ui_theme.DEVICE_CODE_ANSI}WXYZ-1234" in rendered
def test_orchestrator_success_proceeds_and_propagates(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "_offer_github_login", lambda _console: True)
monkeypatch.setattr(
github_login_mod,
"authenticate_and_configure_github",
lambda **_kwargs: GitHubLoginResult(ok=True, username="octocat", detail="OK"),
)
completed: list[str] = []
monkeypatch.setattr(flg, "capture_github_login_completed", completed.append)
cleared: list[bool] = []
monkeypatch.setattr(flg, "clear_github_login_deferral", lambda: cleared.append(True))
proceed = flg.require_github_login_on_first_launch(_console())
assert proceed is True
assert completed == ["octocat"]
assert cleared == [True]
def test_orchestrator_skip_at_menu_proceeds(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "_offer_github_login", lambda _console: False)
deferred: list[bool] = []
monkeypatch.setattr(flg, "write_github_login_deferred", deferred.append)
proceed = flg.require_github_login_on_first_launch(_console())
assert proceed is True
assert deferred == [True]
def test_orchestrator_escape_during_wait_proceeds(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "_offer_github_login", lambda _console: True)
def _raise_cancel(**_kwargs: object) -> GitHubLoginResult:
raise KeyboardInterrupt
monkeypatch.setattr(github_login_mod, "authenticate_and_configure_github", _raise_cancel)
deferred: list[bool] = []
monkeypatch.setattr(flg, "write_github_login_deferred", deferred.append)
proceed = flg.require_github_login_on_first_launch(_console())
assert proceed is True
assert deferred == [True]
def test_orchestrator_failure_then_decline_retry_skips(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "_offer_github_login", lambda _console: True)
monkeypatch.setattr(
github_login_mod,
"authenticate_and_configure_github",
lambda **_kwargs: GitHubLoginResult(ok=False, detail="cannot verify"),
)
monkeypatch.setattr(flg, "_ask_retry", lambda _console: False)
deferred: list[bool] = []
monkeypatch.setattr(flg, "write_github_login_deferred", deferred.append)
proceed = flg.require_github_login_on_first_launch(_console())
assert proceed is True
assert deferred == [True]
def test_orchestrator_retries_until_success(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "_offer_github_login", lambda _console: True)
calls = {"n": 0}
def _login(**_kwargs: object) -> GitHubLoginResult:
calls["n"] += 1
if calls["n"] == 1:
return GitHubLoginResult(ok=False, detail="cannot verify")
return GitHubLoginResult(ok=True, username="octocat", detail="OK")
monkeypatch.setattr(github_login_mod, "authenticate_and_configure_github", _login)
monkeypatch.setattr(flg, "_ask_retry", lambda _console: True)
monkeypatch.setattr(flg, "capture_github_login_completed", lambda _username: None)
monkeypatch.setattr(flg, "clear_github_login_deferral", lambda: None)
proceed = flg.require_github_login_on_first_launch(_console())
assert proceed is True
assert calls["n"] == 2
def test_clear_github_login_deferral_noop_when_not_deferred(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(flg, "read_github_login_deferred", lambda: False)
writes: list[bool] = []
monkeypatch.setattr(flg, "write_github_login_deferred", writes.append)
flg.clear_github_login_deferral()
assert writes == []
def test_clear_github_login_deferral_clears_when_deferred(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg, "read_github_login_deferred", lambda: True)
writes: list[bool] = []
monkeypatch.setattr(flg, "write_github_login_deferred", writes.append)
flg.clear_github_login_deferral()
assert writes == [False]
def test_sleep_until_or_cancel_raises_on_escape(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(flg.os, "name", "posix")
monkeypatch.setattr(flg.sys.stdin, "isatty", lambda: True)
monkeypatch.setattr(flg.sys.stdin, "fileno", lambda: 0)
monkeypatch.setattr(
"surfaces.interactive_shell.ui.components.key_reader.read_key_unix",
lambda **_kwargs: "cancel",
)
monkeypatch.setattr("termios.tcgetattr", lambda _fd: [0] * 7)
monkeypatch.setattr("tty.setraw", lambda _fd: None)
restored: list[bool] = []
monkeypatch.setattr(
"termios.tcsetattr",
lambda _fd, _when, _attrs: restored.append(True),
)
def _ready(
_fd: int, *_args: object, **_kwargs: object
) -> tuple[list[int], list[int], list[int]]:
return ([0], [], [])
monkeypatch.setattr("select.select", _ready)
with pytest.raises(KeyboardInterrupt):
flg._sleep_until_or_cancel(1.0)
assert restored == [True]