Files
tracer-cloud--opensre/tests/interactive_shell/runtime/test_subprocess_runner.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

1607 lines
50 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Direct unit tests for ``subprocess_runner`` (complement to ``test_agent_actions``)."""
from __future__ import annotations
import errno
import io
import subprocess
import tempfile
from pathlib import Path, PurePosixPath
from unittest.mock import MagicMock
import pytest
from rich.console import Console
from integrations.llm_cli.base import CLIInvocation, CLIProbe
from platform.common.task_types import TaskKind, TaskStatus
from surfaces.interactive_shell.runtime.subprocess_runner import (
_MIN_SUBPROCESS_TERMINAL_WIDTH,
_TASK_OUTPUT_PREFIX_WIDTH,
_is_interactive_wizard,
_pump_task_pty,
_pump_task_stream,
read_diag,
read_task_output,
run_opensre_cli_command,
start_background_cli_task,
terminate_child_process,
)
from surfaces.interactive_shell.runtime.subprocess_runner.repl_presenter import make_repl_presenter
from surfaces.interactive_shell.session import Session
from tools.interactive_shell.implementation.claude_code_executor import (
run_claude_code_implementation,
)
from tools.interactive_shell.shell.execution import (
ShellExecutionResult,
)
from tools.interactive_shell.shell.runner import (
run_cd_command,
run_pwd_command,
run_shell_command,
)
from tools.interactive_shell.synthetic.runner import (
run_synthetic_test,
watch_synthetic_subprocess,
)
_BACKGROUND_TASK_POPEN = "surfaces.interactive_shell.runtime.subprocess_runner.subprocess.Popen"
_CLI_POPEN = "tools.interactive_shell.cli.subprocess.Popen"
_CLI_RUN = "tools.interactive_shell.cli.subprocess.run"
_SYNTHETIC_RUNNER_POPEN = "tools.interactive_shell.synthetic.runner.subprocess.Popen"
def _presenter(
session: Session,
console: Console,
*,
confirm_fn: object = None,
is_tty: bool | None = None,
action_already_listed: bool = False,
):
return make_repl_presenter(
session,
console,
confirm_fn=confirm_fn,
is_tty=is_tty,
action_already_listed=action_already_listed,
)
class _ImmediateThread:
def __init__(
self,
group: object = None,
target: object = None,
name: object = None,
args: tuple[object, ...] = (),
kwargs: dict[str, object] | None = None,
*,
daemon: object = None,
) -> None:
del group, name, daemon
if not callable(target):
raise TypeError("target required")
self._target = target
self._args = args
self._kwargs = kwargs or {}
def start(self) -> None:
self._target(*self._args, **self._kwargs)
def join(self, timeout: float | None = None) -> None:
self.join_timeout = timeout
def test_terminate_child_process_noop_when_exited() -> None:
proc = MagicMock()
proc.poll.return_value = 0
terminate_child_process(proc)
proc.terminate.assert_not_called()
def test_read_diag_respects_byte_cap() -> None:
with tempfile.SpooledTemporaryFile() as buf: # type: ignore[type-arg]
buf.write(b"z" * 5_000)
text = read_diag(buf)
assert len(text) == 2_000
def test_read_task_output_handles_none_buffer() -> None:
assert read_task_output(None, limit=100) == ""
def test_read_task_output_strips_ansi_and_caps() -> None:
with tempfile.SpooledTemporaryFile() as buf: # type: ignore[type-arg]
buf.write(b"\x1b[31merror\x1b[0m line\n")
text = read_task_output(buf, limit=1_000)
assert text == "error line"
def test_read_task_output_returns_empty_for_closed_buffer() -> None:
with tempfile.SpooledTemporaryFile() as buf: # type: ignore[type-arg]
buf.write(b"data")
# Buffer is closed once the ``with`` block exits.
assert read_task_output(buf, limit=100) == ""
def test_run_pwd_command_prints_cwd(monkeypatch: pytest.MonkeyPatch) -> None:
def _fake_cwd(_: type[Path]) -> PurePosixPath:
return PurePosixPath("/shown/pwd")
monkeypatch.setattr(Path, "cwd", classmethod(_fake_cwd))
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_pwd_command("pwd", _presenter(session, console))
assert "/shown/pwd" in buf.getvalue()
assert session.history[-1]["type"] == "shell"
def test_run_pwd_command_rejects_multiple_tokens() -> None:
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_pwd_command("pwd extra", _presenter(session, console))
assert "too many arguments" in buf.getvalue().lower()
assert session.history[-1]["ok"] is False
def test_run_cd_command_chdirs_to_target(monkeypatch: pytest.MonkeyPatch) -> None:
directories: list[Path] = []
def _chdir(target: Path) -> None:
directories.append(target)
monkeypatch.setattr(
"tools.interactive_shell.shell.runner.os.chdir",
_chdir,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_cd_command("cd /tmp/example", _presenter(session, console))
assert directories == [Path("/tmp/example")]
assert session.history[-1]["type"] == "shell"
def test_run_cd_command_reports_chdir_failure(monkeypatch: pytest.MonkeyPatch) -> None:
captured_errors: list[BaseException] = []
def _chdir(_target: Path) -> None:
raise OSError("permission denied")
monkeypatch.setattr(
"tools.interactive_shell.shell.runner.os.chdir",
_chdir,
)
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_cd_command("cd /root/blocked", _presenter(session, console))
assert "cd failed" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], OSError)
assert session.history[-1] == {
"type": "shell",
"text": "cd /root/blocked",
"ok": False,
"response_text": "cd failed: permission denied",
}
def test_run_shell_command_records_when_input_is_empty() -> None:
"""Alpha mode allows every command; only empty input is rejected.
A bare ``!`` has nothing to run, so the REPL prints a ``blocked`` notice,
never echoes a command, and records the attempt as ``ok=False``. (Restricted
commands like ``sudo`` are no longer blocked — there is no deny floor.)
"""
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_shell_command(
"!",
_presenter(session, console, confirm_fn=lambda _p: "n", is_tty=True),
)
out = buf.getvalue()
assert "blocked" in out.lower()
# Nothing should be echoed/executed for empty input.
assert "$ " not in out
assert session.history[-1] == {"type": "shell", "text": "!", "ok": False}
def test_run_claude_code_implementation_starts_tracked_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_calls: list[tuple[list[str], dict[str, object]]] = []
stdin_seen: list[str | None] = []
class _FakeAdapter:
def detect(self) -> CLIProbe:
return CLIProbe(
installed=True,
version="1.2.3",
logged_in=True,
bin_path="/usr/local/bin/claude",
detail="ok",
)
def build(
self,
*,
prompt: str,
model: str | None,
workspace: str,
reasoning_effort: str | None = None,
) -> CLIInvocation:
assert model is None
assert workspace
assert reasoning_effort is None
assert "Recent OpenSRE terminal assistant context" in prompt
assert "Process auto-discovery" in prompt
assert "Do not create a git commit" in prompt
return CLIInvocation(
argv=("/usr/local/bin/claude", "-p", "--output-format", "text"),
stdin=prompt,
cwd=workspace,
env={"CLAUDE_TEST": "1"},
timeout_sec=120.0,
)
class _FakeProcess:
returncode = 0
def communicate(
self,
input: str | None = None,
timeout: int | None = None,
) -> tuple[str, str]:
assert timeout is not None
stdin_seen.append(input)
return "changed interactive_shell\n", ""
def poll(self) -> int:
return 0
def _fake_popen(command: list[str], **kwargs: object) -> _FakeProcess:
popen_calls.append((command, kwargs))
return _FakeProcess()
monkeypatch.delenv("CLAUDE_CODE_IMPLEMENT_PERMISSION_MODE", raising=False)
monkeypatch.delenv("CLAUDE_CODE_MODEL", raising=False)
monkeypatch.setattr(
"tools.interactive_shell.implementation.claude_code_executor.ClaudeCodeAdapter",
_FakeAdapter,
)
monkeypatch.setattr(
"tools.interactive_shell.implementation.claude_code_executor.subprocess.Popen",
_fake_popen,
)
monkeypatch.setattr(
"tools.interactive_shell.implementation.claude_code_executor.threading.Thread",
_ImmediateThread,
)
session = Session()
session.agent.messages.append(
("assistant", "Process auto-discovery should scan local agent processes.")
)
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_claude_code_implementation(
"implement",
_presenter(session, console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert len(popen_calls) == 1
command, kwargs = popen_calls[0]
assert command == [
"/usr/local/bin/claude",
"-p",
"--output-format",
"text",
"--permission-mode",
"acceptEdits",
]
assert kwargs["cwd"]
assert stdin_seen and "Process auto-discovery" in stdin_seen[0]
assert session.history[-1] == {"type": "implementation", "text": "implement", "ok": True}
task = session.task_registry.list_recent(1)[0]
assert task.kind == TaskKind.CODE_AGENT
assert task.status == TaskStatus.COMPLETED
out = buf.getvalue()
assert "Claude Code started" in out
assert "Claude Code completed" in out
def test_run_claude_code_implementation_rejects_vague_request_without_context() -> None:
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_claude_code_implementation(
"implement",
_presenter(session, console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert "too vague" in buf.getvalue()
assert session.history[-1] == {"type": "implementation", "text": "implement", "ok": False}
assert session.task_registry.list_recent(1) == []
def test_run_shell_command_silent_success_prints_checkmark(monkeypatch: pytest.MonkeyPatch) -> None:
def _fake_execute(**_kwargs: object) -> ShellExecutionResult:
return ShellExecutionResult(
command="true",
argv=["true"],
stdout="",
stderr="",
exit_code=0,
timed_out=False,
truncated=False,
executed_with_shell=False,
)
monkeypatch.setattr(
"tools.interactive_shell.shell.execution.execute_shell_command",
_fake_execute,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_shell_command("true", _presenter(session, console))
assert "✓" in buf.getvalue()
assert session.history[-1] == {"type": "shell", "text": "true", "ok": True}
def test_run_shell_command_success_records_stdout_without_stderr_noise(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def _fake_execute(**_kwargs: object) -> ShellExecutionResult:
return ShellExecutionResult(
command="curl wttr.in/Hawaii?format=3",
argv=["curl", "wttr.in/Hawaii?format=3"],
stdout="Hawaii: +25C\n",
stderr="curl progress\n",
exit_code=0,
timed_out=False,
truncated=False,
executed_with_shell=False,
)
monkeypatch.setattr(
"tools.interactive_shell.shell.execution.execute_shell_command",
_fake_execute,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
result = run_shell_command("curl wttr.in/Hawaii?format=3", _presenter(session, console))
assert session.history[-1] == {
"type": "shell",
"text": "curl wttr.in/Hawaii?format=3",
"ok": True,
"response_text": "Hawaii: +25C",
}
assert result["ok"] is True
assert result["stdout"] == "Hawaii: +25C"
assert result["stderr"] == ""
assert result["response_text"] == "Hawaii: +25C"
def test_run_shell_command_failure_prints_exit_line(monkeypatch: pytest.MonkeyPatch) -> None:
def _fake_execute(**_kwargs: object) -> ShellExecutionResult:
return ShellExecutionResult(
command="false",
argv=["false"],
stdout="",
stderr="",
exit_code=7,
timed_out=False,
truncated=False,
executed_with_shell=False,
)
monkeypatch.setattr(
"tools.interactive_shell.shell.execution.execute_shell_command",
_fake_execute,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_shell_command("false", _presenter(session, console))
out = buf.getvalue()
assert "✗" in out
assert "exit 7" in out
assert session.history[-1] == {
"type": "shell",
"text": "false",
"ok": False,
"response_text": "✗ exit 7",
}
def test_run_shell_command_reports_start_failure(monkeypatch: pytest.MonkeyPatch) -> None:
captured_errors: list[BaseException] = []
def _raise(**_kwargs: object) -> ShellExecutionResult:
raise RuntimeError("spawn failed")
monkeypatch.setattr(
"tools.interactive_shell.shell.execution.execute_shell_command",
_raise,
)
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_shell_command("true", _presenter(session, console))
assert "command failed to start" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
assert session.history[-1] == {
"type": "shell",
"text": "true",
"ok": False,
"response_text": "command failed to start: spawn failed",
}
def test_run_opensre_agents_scan_prints_clean_foreground_output(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def _fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
assert command[-2:] == ["fleet", "scan"]
assert kwargs["capture_output"] is True
return subprocess.CompletedProcess(
args=command,
returncode=0,
stdout="agent scan\n777 claude-code-777 claude code\nNext: register\n",
stderr="",
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.subprocess.run",
_fake_run,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
assert run_opensre_cli_command("fleet scan", session, console) is True
out = buf.getvalue()
assert "$ opensre fleet scan" in out
assert "agent scan" in out
assert "777 claude-code-777 claude code" in out
assert "started." not in out
assert "stdout │" not in out
assert session.history[-1] == {"type": "cli_command", "text": "opensre fleet scan", "ok": True}
def test_run_opensre_agents_scan_register_explains_confirmation(
monkeypatch: pytest.MonkeyPatch,
) -> None:
def _fake_run(command: list[str], **_kwargs: object) -> subprocess.CompletedProcess[str]:
return subprocess.CompletedProcess(
args=command,
returncode=0,
stdout="registered 1 agent(s)\n",
stderr="",
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.subprocess.run",
_fake_run,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
assert (
run_opensre_cli_command(
"fleet scan --register",
session,
console,
confirm_fn=lambda _prompt: "y",
is_tty=True,
)
is True
)
out = buf.getvalue()
assert "register discovered local AI-agent processes" in out
assert "registered 1 agent(s)" in out
assert "stdout │" not in out
assert session.history[-1] == {
"type": "cli_command",
"text": "opensre fleet scan --register",
"ok": True,
}
def test_run_opensre_agents_watch_runs_in_foreground(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_kwargs: list[dict[str, object]] = []
class _FakeProcess:
stdout = iter(["watching pid 1234; press Ctrl+C to stop\n", "pid 1234 exited\n"])
def wait(self) -> int:
return 0
def _fake_popen(command: list[str], **kwargs: object) -> _FakeProcess:
assert command[-3:] == ["fleet", "watch", "1234"]
popen_kwargs.append(kwargs)
return _FakeProcess()
monkeypatch.setattr(
_CLI_POPEN,
_fake_popen,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
assert (
run_opensre_cli_command(
"fleet watch 1234",
session,
console,
confirm_fn=lambda _prompt: "y",
is_tty=True,
)
is True
)
out = buf.getvalue()
assert "$ opensre fleet watch 1234" in out
assert "watching pid 1234" in out
assert "pid 1234 exited" in out
assert "started" not in out
assert "timeout" not in popen_kwargs[0]
assert popen_kwargs[0]["stderr"] is subprocess.STDOUT
assert session.task_registry.list_recent() == []
assert session.history[-1] == {
"type": "cli_command",
"text": "opensre fleet watch 1234",
"ok": True,
}
def test_start_background_cli_task_uses_pty_for_live_terminal_output(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_kwargs: list[dict[str, object]] = []
closed_fds: list[int] = []
chunks = [b"live progress\r\n"]
class _TtyBuffer(io.StringIO):
def isatty(self) -> bool:
return True
class _FakeProcess:
returncode = 0
stdout = None
stderr = None
def poll(self) -> int:
return 0
def _fake_popen(_command: list[str], **kwargs: object) -> _FakeProcess:
popen_kwargs.append(kwargs)
return _FakeProcess()
def _fake_read(fd: int, _size: int) -> bytes:
assert fd == 10
if chunks:
return chunks.pop(0)
raise OSError(errno.EIO, "pty closed")
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.openpty",
lambda: (10, 11),
raising=False,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.read",
_fake_read,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.close",
lambda fd: closed_fds.append(fd),
)
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = _TtyBuffer()
console = Console(file=buf, force_terminal=True)
task = start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["python", "-m", "cli", "tests", "synthetic"],
session=session,
console=console,
kind=TaskKind.SYNTHETIC_TEST,
use_pty=True,
)
assert task is not None
assert task.status == TaskStatus.COMPLETED
assert popen_kwargs[0]["stdout"] == 11
assert popen_kwargs[0]["stderr"] == 11
assert "text" not in popen_kwargs[0]
assert "live progress" in buf.getvalue()
assert 10 in closed_fds
assert 11 in closed_fds
def test_start_background_cli_task_falls_back_to_pipes_when_pty_unavailable(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_kwargs: list[dict[str, object]] = []
class _TtyBuffer(io.StringIO):
def isatty(self) -> bool:
return True
class _FakeProcess:
returncode = 0
stdout = io.StringIO("pipe progress\n")
stderr = io.StringIO("")
def poll(self) -> int:
return 0
def _fake_popen(_command: list[str], **kwargs: object) -> _FakeProcess:
popen_kwargs.append(kwargs)
return _FakeProcess()
monkeypatch.delattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.openpty",
raising=False,
)
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = _TtyBuffer()
console = Console(file=buf, force_terminal=True)
task = start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["python", "-m", "cli", "tests", "synthetic"],
session=session,
console=console,
kind=TaskKind.SYNTHETIC_TEST,
use_pty=True,
)
assert task is not None
assert task.status == TaskStatus.COMPLETED
assert popen_kwargs[0]["stdout"] is subprocess.PIPE
assert popen_kwargs[0]["stderr"] is subprocess.PIPE
assert popen_kwargs[0]["text"] is True
assert "pipe progress" in buf.getvalue()
def test_start_background_cli_task_logs_failure_outcome_to_posthog(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A background CLI task that fails asynchronously must still send its real
stderr/exit outcome to the prompt-log/PostHog sink. This is the regression
for ``opensre investigate`` errors arriving after the turn recorder flushed.
"""
captured: list[dict[str, object]] = []
monkeypatch.setenv("OPENSRE_PROMPT_LOG_REDACT", "0")
monkeypatch.setenv("OPENSRE_PROMPT_LOG_LOCAL_DISABLED", "1")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.telemetry.recorder.capture_ai_generation",
lambda properties: captured.append(properties),
)
class _FakeProcess:
returncode = 1
stdout = io.StringIO("")
stderr = io.StringIO("No remote named 'myportfolio' is configured.\n")
def poll(self) -> int:
return 1
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
lambda _command, **_kwargs: _FakeProcess(),
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
task = start_background_cli_task(
display_command="opensre investigate --service myportfolio",
argv_list=["python", "-m", "cli", "investigate", "--service", "myportfolio"],
session=session,
console=console,
kind=TaskKind.CLI_COMMAND,
)
assert task is not None
assert task.status == TaskStatus.FAILED
assert len(captured) == 1
props = captured[0]
assert props["cli_turn_kind"] == "background_task"
assert props["$ai_trace_id"] == task.task_id
ai_input = props["$ai_input"]
assert isinstance(ai_input, list)
assert ai_input[0]["content"] == "opensre investigate --service myportfolio"
choices = props["$ai_output_choices"]
assert isinstance(choices, list)
content = choices[0]["content"]
assert "command failed (exit 1)" in content
assert "No remote named 'myportfolio' is configured." in content
def test_start_background_cli_task_logs_success_outcome_to_posthog(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""A successful background task logs its stdout outcome, not an empty reply."""
captured: list[dict[str, object]] = []
monkeypatch.setenv("OPENSRE_PROMPT_LOG_REDACT", "0")
monkeypatch.setenv("OPENSRE_PROMPT_LOG_LOCAL_DISABLED", "1")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.telemetry.recorder.capture_ai_generation",
lambda properties: captured.append(properties),
)
class _FakeProcess:
returncode = 0
stdout = io.StringIO("investigation complete: root cause identified\n")
stderr = io.StringIO("")
def poll(self) -> int:
return 0
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
lambda _command, **_kwargs: _FakeProcess(),
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
task = start_background_cli_task(
display_command="opensre investigate --service checkout",
argv_list=["python", "-m", "cli", "investigate", "--service", "checkout"],
session=session,
console=console,
kind=TaskKind.CLI_COMMAND,
)
assert task is not None
assert task.status == TaskStatus.COMPLETED
assert len(captured) == 1
content = captured[0]["$ai_output_choices"][0]["content"]
assert "command completed (exit 0)" in content
assert "investigation complete: root cause identified" in content
def test_task_output_stream_reports_unexpected_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured_errors: list[BaseException] = []
class _BrokenStream:
def __iter__(self) -> _BrokenStream:
return self
def __next__(self) -> str:
raise RuntimeError("stream broke")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
session = Session()
task = session.task_registry.create(TaskKind.CLI_COMMAND, command="demo")
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
_pump_task_stream(
task=task,
stream_name="stdout",
stream=_BrokenStream(), # type: ignore[arg-type]
console=console,
)
assert "stream ended unexpectedly" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
def test_task_pty_stream_reports_unexpected_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured_errors: list[BaseException] = []
closed_fds: list[int] = []
def _raise_read(_fd: int, _size: int) -> bytes:
raise RuntimeError("pty broke")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.read",
_raise_read,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.os.close",
lambda fd: closed_fds.append(fd),
)
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
with tempfile.SpooledTemporaryFile() as capture: # type: ignore[type-arg]
_pump_task_pty(master_fd=123, console=console, capture=capture)
assert "terminal stream ended unexpectedly" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
assert closed_fds == [123]
def test_start_background_cli_task_reports_spawn_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured_errors: list[BaseException] = []
def _fake_popen(_command: list[str], **_kwargs: object) -> object:
raise RuntimeError("spawn broke")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
_fake_popen,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
task = start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["python", "-m", "cli", "tests", "synthetic"],
session=session,
console=console,
kind=TaskKind.SYNTHETIC_TEST,
)
assert task is None
assert "failed to start" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
assert session.task_registry.list_recent(1)[0].status == TaskStatus.FAILED
def test_start_background_cli_task_reports_watcher_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured_errors: list[BaseException] = []
class _FakeProcess:
stdout = None
stderr = None
returncode = 1
def poll(self) -> int:
return 1
def _fake_popen(_command: list[str], **_kwargs: object) -> _FakeProcess:
return _FakeProcess()
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.read_diag",
lambda _buf: (_ for _ in ()).throw(RuntimeError("diag broke")),
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
task = start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["python", "-m", "cli", "tests", "synthetic"],
session=session,
console=console,
kind=TaskKind.SYNTHETIC_TEST,
)
assert task is not None
assert task.status == TaskStatus.FAILED
assert "error:" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
def test_start_background_cli_task_skips_follow_up_after_session_reset(
monkeypatch: pytest.MonkeyPatch,
) -> None:
class _FakeProcess:
stdout = None
stderr = None
returncode = 1
def poll(self) -> int:
return 1
class _DeferredThread:
pending: list[object] = []
def __init__(
self,
group: object = None,
target: object = None,
name: object = None,
args: tuple[object, ...] = (),
kwargs: dict[str, object] | None = None,
*,
daemon: object = None,
) -> None:
del group, name, daemon, args, kwargs
if callable(target):
_DeferredThread.pending.append(target)
def start(self) -> None:
return
def _fake_popen(_command: list[str], **_kwargs: object) -> _FakeProcess:
return _FakeProcess()
_DeferredThread.pending.clear()
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_DeferredThread,
)
monkeypatch.setattr(
_BACKGROUND_TASK_POPEN,
_fake_popen,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
task = start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["python", "-m", "cli", "tests", "synthetic"],
session=session,
console=console,
kind=TaskKind.SYNTHETIC_TEST,
)
assert task is not None
assert len(_DeferredThread.pending) == 1
session.clear()
_DeferredThread.pending[0]() # type: ignore[operator]
assert session.terminal.pending_prompt_default is None
_DeferredThread.pending.clear()
def test_watch_synthetic_subprocess_reports_daemon_failure(
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured_errors: list[BaseException] = []
class _FakeProcess:
stdout = None
stderr = None
def poll(self) -> int:
raise RuntimeError("poll broke")
monkeypatch.setattr(
"surfaces.interactive_shell.utils.error_handling.exception_reporting.capture_exception",
lambda exc, **_kwargs: captured_errors.append(exc),
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
task = session.task_registry.create(TaskKind.SYNTHETIC_TEST, command="suite")
task.mark_running()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
with tempfile.SpooledTemporaryFile() as stderr_buf: # type: ignore[type-arg]
watch_synthetic_subprocess(
task,
_FakeProcess(), # type: ignore[arg-type]
_presenter(session, console),
"suite:001-test",
stderr_buf,
)
assert task.status == TaskStatus.FAILED
assert "synthetic watcher failed" in buf.getvalue()
assert len(captured_errors) == 1
assert isinstance(captured_errors[0], RuntimeError)
def test_run_synthetic_test_unknown_suite_records_failure() -> None:
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_synthetic_test("nonexistent_suite", _presenter(session, console))
assert "unknown synthetic" in buf.getvalue().lower()
entry = session.history[-1]
assert entry["type"] == "synthetic_test"
assert entry["ok"] is False
def test_run_synthetic_test_streams_subprocess_output(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_kwargs: list[dict[str, object]] = []
popen_commands: list[list[str]] = []
class _FakeProcess:
returncode = 0
stdout = io.StringIO("collecting fixtures\nrunning investigation\n")
stderr = io.StringIO("warning: slow cloudwatch response\n")
def poll(self) -> int:
return 0
def _fake_popen(command: list[str], **kwargs: object) -> _FakeProcess:
popen_commands.append(command)
popen_kwargs.append(kwargs)
return _FakeProcess()
monkeypatch.setattr(
_SYNTHETIC_RUNNER_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_synthetic_test(
"rds_postgres",
_presenter(session, console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert popen_commands[0][1] == "-u"
assert popen_commands[0][-2:] == ["--scenario", "001-replication-lag"]
assert popen_kwargs[0]["stdout"] is not None
assert popen_kwargs[0]["stderr"] is not None
assert popen_kwargs[0]["text"] is True
out = buf.getvalue()
assert "collecting fixtures" in out
assert "running investigation" in out
assert "warning: slow cloudwatch response" in out
task = session.task_registry.list_recent(1)[0]
assert task.status == TaskStatus.COMPLETED
def test_run_synthetic_test_honours_explicit_scenario(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_commands: list[list[str]] = []
class _FakeProcess:
returncode = 0
stdout = io.StringIO("scenario run\n")
stderr = io.StringIO("")
def poll(self) -> int:
return 0
def _fake_popen(command: list[str], **_kwargs: object) -> _FakeProcess:
popen_commands.append(command)
return _FakeProcess()
monkeypatch.setattr(
_SYNTHETIC_RUNNER_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_synthetic_test(
"rds_postgres:005-failover",
_presenter(session, console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert popen_commands[0][-2:] == ["--scenario", "005-failover"]
assert "opensre tests synthetic --scenario 005-failover" in buf.getvalue()
def test_run_synthetic_test_all_launches_suite_alias(
monkeypatch: pytest.MonkeyPatch,
) -> None:
popen_commands: list[list[str]] = []
class _FakeProcess:
returncode = 0
stdout = io.StringIO("scenario run\n")
stderr = io.StringIO("")
def poll(self) -> int:
return 0
def _fake_popen(command: list[str], **_kwargs: object) -> _FakeProcess:
popen_commands.append(command)
return _FakeProcess()
monkeypatch.setattr(
_SYNTHETIC_RUNNER_POPEN,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
run_synthetic_test(
"rds_postgres:all",
_presenter(session, console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert popen_commands[0][-2:] == ["synthetic", "all"]
assert "opensre tests synthetic all" in buf.getvalue()
# ─────────────────────────────────────────────────────────────────────────────
# Subprocess terminal width forwarding
# ─────────────────────────────────────────────────────────────────────────────
#
# Regression: subprocess Rich output (synthetic suite panels and tables) used
# to render at the default 80-column width because the subprocess's stdout is
# a pipe. ``_print_task_output_line`` then prepended an 18-char ``<task_id>
# <stream> │ `` prefix, producing 98-char lines that wrapped mid-row in the
# user's narrower terminal — the visible symptom was broken table headers and
# panel borders. We forward ``user_width - prefix - 1`` via ``COLUMNS`` so the
# subprocess renders narrow enough that the relayed line fits intact.
class _CapturedPopen:
returncode = 0
stdout = io.StringIO("")
stderr = io.StringIO("")
def poll(self) -> int:
return 0
def _capture_popen_kwargs(
monkeypatch: pytest.MonkeyPatch,
*,
popen_target: str = _BACKGROUND_TASK_POPEN,
) -> list[dict[str, object]]:
captured: list[dict[str, object]] = []
def _fake_popen(_command: list[str], **kwargs: object) -> _CapturedPopen:
captured.append(kwargs)
return _CapturedPopen()
monkeypatch.setattr(
popen_target,
_fake_popen,
)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.threading.Thread",
_ImmediateThread,
)
return captured
def _start_one_task(console: Console) -> None:
start_background_cli_task(
display_command="opensre tests synthetic --scenario 001-replication-lag",
argv_list=["opensre", "tests", "synthetic", "--scenario", "001-replication-lag"],
session=Session(),
console=console,
kind=TaskKind.SYNTHETIC_TEST,
)
def test_background_task_forwards_columns_minus_prefix_to_subprocess(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Subprocess COLUMNS = user_width task-output prefix 1.
A 100-col user terminal must hand the subprocess ``100 18 1 = 81``
columns so its Rich rendering fits within the user's terminal once the
18-char ``<task_id> stdout │ `` prefix is prepended by the line pump.
"""
captured = _capture_popen_kwargs(monkeypatch)
console = Console(file=io.StringIO(), force_terminal=False, width=100)
_start_one_task(console)
assert captured, "Popen must have been called"
env = captured[0].get("env")
assert isinstance(env, dict)
assert env.get("COLUMNS") == str(100 - _TASK_OUTPUT_PREFIX_WIDTH - 1)
def test_background_task_floors_subprocess_columns_for_tiny_terminals(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""COLUMNS must never go below ``_MIN_SUBPROCESS_TERMINAL_WIDTH``.
On absurdly narrow terminals (CI jobs, restricted SSH PTYs) the naive
``width prefix 1`` calculation could fall below the point where Rich
panels render at all. We floor at 60 so borders remain drawable; visible
wrapping is better than a crushed rendering.
"""
captured = _capture_popen_kwargs(monkeypatch)
console = Console(file=io.StringIO(), force_terminal=False, width=40)
_start_one_task(console)
env = captured[0].get("env")
assert isinstance(env, dict)
assert env.get("COLUMNS") == str(_MIN_SUBPROCESS_TERMINAL_WIDTH)
def test_background_task_preserves_existing_environment(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The forwarded env must inherit ``os.environ`` so the subprocess sees PATH etc.
We only inject COLUMNS/LINES; everything else (PATH, HOME, virtualenv,
auth tokens) must reach the synthetic suite unchanged.
"""
monkeypatch.setenv("OPENSRE_TEST_MARKER", "preserved-value")
captured = _capture_popen_kwargs(monkeypatch)
console = Console(file=io.StringIO(), force_terminal=False, width=120)
_start_one_task(console)
env = captured[0].get("env")
assert isinstance(env, dict)
assert env.get("OPENSRE_TEST_MARKER") == "preserved-value"
def test_run_synthetic_test_forwards_columns_to_subprocess(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``run_synthetic_test`` opens its own Popen — it must also forward COLUMNS.
Regression: this code path bypasses ``start_background_cli_task`` and so
was missed by the first iteration of the width-forwarding fix. The
visible symptom was that the synthetic suite's Rich Panel and Table
output (the per-scenario "Synthetic RDS Run" panel, the "Synthetic
Suite Report" table, and the "Level Summary" table) rendered at the
pipe-default 80 columns and then wrapped mid-row in the user's terminal
once the 18-char ``<task_id> stdout │ `` prefix had been prepended.
"""
captured = _capture_popen_kwargs(monkeypatch, popen_target=_SYNTHETIC_RUNNER_POPEN)
console = Console(file=io.StringIO(), force_terminal=False, width=110)
run_synthetic_test(
"rds_postgres:005-failover",
_presenter(Session(), console, confirm_fn=lambda _prompt: "y", is_tty=True),
)
assert captured, "run_synthetic_test must spawn at least one subprocess"
env = captured[0].get("env")
assert isinstance(env, dict)
assert env.get("COLUMNS") == str(110 - _TASK_OUTPUT_PREFIX_WIDTH - 1)
@pytest.mark.parametrize(
"tokens,expected",
[
# Single-token interactive wizards.
(["onboard"], True),
(["ONBOARD"], True), # case-insensitive
(["onboard", "local_llm"], True), # extra args still classified
# Two-token interactive wizard.
(["integrations", "setup"], True),
(["INTEGRATIONS", "SETUP"], True),
(["integrations", "setup", "datadog"], True),
# Two-token NON-wizard under integrations — must NOT match.
(["integrations", "list"], False),
(["integrations", "verify"], False),
# Other subcommands — must NOT match.
(["health"], False),
(["version"], False),
(["fleet", "list"], False),
# Edge: empty.
([], False),
],
)
def test_is_interactive_wizard_classifies_command_paths(tokens: list[str], expected: bool) -> None:
"""The wizard classifier is the data-driven contract for the LLM-classified
path (``cli_exec`` tool). When the LLM tries to invoke a wizard via
``cli_exec``, we redirect the user to the equivalent slash command instead
because exclusive stdin is only guaranteed for the slash-command path.
Adding a new interactive command later should be a one-line set entry —
this test pins the current set + the case-insensitive lookup behavior.
"""
assert _is_interactive_wizard(tokens) is expected
def test_run_opensre_cli_command_refuses_onboard_with_helpful_message(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The LLM-classified path (``cli_exec`` tool) for ``opensre onboard``
must not spawn a subprocess — exclusive stdin is not guaranteed when the
LLM planner is involved. It must instead print a message directing the
user to the ``/onboard`` slash command, which does have exclusive stdin.
"""
popen_calls: list[list[str]] = []
run_calls: list[list[str]] = []
def _fake_popen(command: list[str], **_kwargs: object) -> None:
popen_calls.append(command)
raise AssertionError("subprocess.Popen must not be called for interactive subcommand")
def _fake_run(command: list[str], **_kwargs: object) -> None:
run_calls.append(command)
raise AssertionError("subprocess.run must not be called for interactive subcommand")
monkeypatch.setattr(
_CLI_POPEN,
_fake_popen,
)
monkeypatch.setattr(
_CLI_RUN,
_fake_run,
)
session = Session()
buf = io.StringIO()
# Width >80 so the multi-line warning doesn't wrap mid-substring on
# the assertions below.
console = Console(file=buf, force_terminal=False, width=200)
assert (
run_opensre_cli_command(
"onboard",
session,
console,
confirm_fn=lambda _prompt: "y",
is_tty=True,
)
is True
)
out = buf.getvalue()
assert "needs a full terminal" in out
assert "opensre onboard" in out
# Directs user to the slash command (not "exit the shell").
assert "/onboard" in out
assert popen_calls == []
assert run_calls == []
assert session.history[-1]["type"] == "cli_command"
assert session.history[-1]["text"] == "opensre onboard"
assert session.history[-1]["ok"] is False
assert "full terminal" in str(session.history[-1].get("response_text", ""))
assert "/onboard" in str(session.history[-1].get("response_text", ""))
def test_run_opensre_cli_command_refuses_integrations_setup_with_helpful_message(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``opensre integrations setup`` via the LLM ``cli_exec`` path must redirect
to ``/integrations setup`` (which has exclusive stdin). Same pattern as ``onboard``.
"""
popen_calls: list[list[str]] = []
run_calls: list[list[str]] = []
monkeypatch.setattr(
_CLI_POPEN,
lambda cmd, **_kw: popen_calls.append(cmd),
)
monkeypatch.setattr(
_CLI_RUN,
lambda cmd, **_kw: run_calls.append(cmd),
)
session = Session()
buf = io.StringIO()
# Width >80 so the multi-line warning doesn't wrap mid-substring on
# assertions below.
console = Console(file=buf, force_terminal=False, width=200)
assert (
run_opensre_cli_command(
"integrations setup",
session,
console,
confirm_fn=lambda _prompt: "y",
is_tty=True,
)
is True
)
out = buf.getvalue()
assert "needs a full terminal" in out
assert "opensre integrations setup" in out
# Directs user to the slash command (not "exit the shell").
assert "/integrations setup" in out
assert popen_calls == []
assert run_calls == []
assert session.history[-1]["type"] == "cli_command"
assert session.history[-1]["text"] == "opensre integrations setup"
assert session.history[-1]["ok"] is False
assert "full terminal" in str(session.history[-1].get("response_text", ""))
def test_run_opensre_cli_command_skips_confirmation_for_investigate(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""``opensre investigate`` is the primary REPL purpose — no Proceed? prompt."""
confirm_calls: list[str] = []
start_calls: list[list[str]] = []
def _fake_confirm(prompt: str) -> str:
confirm_calls.append(prompt)
return "n"
def _fake_start_background_cli_task(*, argv_list: list[str], **_kw: object) -> None:
start_calls.append(argv_list)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.start_background_cli_task",
_fake_start_background_cli_task,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
assert (
run_opensre_cli_command(
"investigate -i alert.json",
session,
console,
confirm_fn=_fake_confirm,
is_tty=True,
)
is True
)
assert confirm_calls == []
assert start_calls
assert "investigate" in start_calls[0]
assert "-i" in start_calls[0]
assert "alert.json" in start_calls[0]
assert "Proceed?" not in buf.getvalue()
assert "may change local config" not in buf.getvalue()
def test_run_opensre_cli_command_allows_integrations_list_without_blocking(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Only the ``setup`` subcommand under ``integrations`` is the wizard.
Other ``integrations`` subcommands like ``integrations list`` must
not get caught by the interactive-wizard block — guard against an
over-broad refusal.
"""
start_calls: list[list[str]] = []
def _fake_start_background_cli_task(*, argv_list: list[str], **_kw: object) -> None:
start_calls.append(argv_list)
monkeypatch.setattr(
"surfaces.interactive_shell.runtime.subprocess_runner.start_background_cli_task",
_fake_start_background_cli_task,
)
session = Session()
buf = io.StringIO()
console = Console(file=buf, force_terminal=False)
assert (
run_opensre_cli_command(
"integrations list",
session,
console,
confirm_fn=lambda _prompt: "y",
is_tty=True,
)
is True
)
out = buf.getvalue()
assert "needs a full terminal" not in out
# The dispatcher should have reached the background-task path
# (proving the wizard block didn't fire).
assert start_calls, "background task starter was not invoked"
assert "integrations" in start_calls[0]
assert "list" in start_calls[0]