Files
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

382 lines
13 KiB
Python

from __future__ import annotations
import json
import unittest.mock
from pathlib import Path
import pytest
from click.testing import CliRunner
from surfaces.cli.__main__ import cli
from surfaces.cli.tests.catalog import TestCatalogItem, TestRequirement
from surfaces.cli.tests.runner import format_command, run_catalog_item, run_catalog_items
def test_tests_list_filters_ci_safe_inventory() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--category", "ci-safe"])
assert result.exit_code == 0
assert "make:test-cov" in result.output
assert "make:test-full" in result.output
assert "rca:pipeline_error_in_logs" not in result.output
def test_tests_run_dry_run_prints_command() -> None:
runner = CliRunner()
result = runner.invoke(
cli,
["tests", "run", "make:test-cov", "--dry-run"],
)
assert result.exit_code == 0
assert "make test-cov" in result.output
# --- Always-on discovery ---
def test_tests_list_works_in_non_interactive_env() -> None:
"""opensre tests list must succeed regardless of TUI availability."""
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list"])
assert result.exit_code == 0
assert len(result.output.strip()) > 0
def test_stable_catalog_ids_always_present() -> None:
"""Core catalog IDs must be stable across runs."""
runner = CliRunner()
result = runner.invoke(cli, ["--json", "tests", "list"])
assert result.exit_code == 0
ids = {item["id"] for item in json.loads(result.output)}
assert "make:test-cov" in ids
assert "make:test-full" in ids
# --- Filtering ---
def test_tests_list_search_filter_narrows_results() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--search", "pipeline"])
assert result.exit_code == 0
assert "rca:pipeline_error_in_logs" in result.output
assert "make:test-cov" not in result.output
def test_tests_list_category_synthetic() -> None:
"""--category synthetic must be accepted and return real entries."""
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--category", "synthetic"])
assert result.exit_code == 0
assert "synthetic:001-replication-lag" in result.output
def test_tests_list_category_rca_excludes_make_targets() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--category", "rca"])
assert result.exit_code == 0
assert "make:test-cov" not in result.output
assert "openclaw-synthetic:gateway_process_terminated_missing_tls_key" in result.output
def test_tests_list_category_openclaw_includes_fixture_and_synthetic() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--category", "openclaw"])
assert result.exit_code == 0
assert "rca:openclaw_gateway_crashed" in result.output
assert "openclaw-synthetic:gateway_process_terminated_missing_tls_key" in result.output
def test_tests_list_search_no_match_returns_empty() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "list", "--search", "zzz_no_match_xyz_abc"])
assert result.exit_code == 0
assert result.output.strip() == ""
# --- JSON output ---
def test_tests_list_json_output_has_required_fields() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["--json", "tests", "list"])
assert result.exit_code == 0
data = json.loads(result.output)
assert isinstance(data, list)
assert len(data) > 0
first = data[0]
assert {"id", "name", "tags", "description", "children"} <= set(first.keys())
def test_tests_list_json_filtered_by_category() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["--json", "tests", "list", "--category", "ci-safe"])
assert result.exit_code == 0
data = json.loads(result.output)
ids = {item["id"] for item in data}
assert "make:test-cov" in ids
assert "rca:pipeline_error_in_logs" not in ids
# --- Helpful errors ---
def test_tests_run_unknown_id_gives_helpful_error() -> None:
runner = CliRunner()
result = runner.invoke(cli, ["tests", "run", "make:does-not-exist-xyz"])
assert result.exit_code != 0
output = result.output
assert "does-not-exist-xyz" in output
assert "opensre tests list" in output
def test_tests_no_subcommand_non_interactive_gives_clear_error() -> None:
"""opensre tests with no subcommand in a non-tty env must not traceback."""
runner = CliRunner()
result = runner.invoke(cli, ["tests"])
# Must not exit with an unhandled exception / traceback
assert result.exception is None or isinstance(result.exception, SystemExit)
# Should surface actionable guidance
assert "tests list" in result.output or "tests run" in result.output or result.exit_code != 0
def test_tests_no_subcommand_missing_tui_deps_gives_opensre_error() -> None:
"""When questionary is absent the interactive path degrades to a structured error."""
runner = CliRunner()
with unittest.mock.patch(
"surfaces.cli.tests.interactive._questionary",
None,
):
result = runner.invoke(cli, ["tests"])
# Must not produce a raw traceback — exit with a structured error
assert result.exception is None or isinstance(result.exception, SystemExit)
output = result.output
assert "traceback" not in output.lower()
assert "tests list" in output or "tests run" in output or result.exit_code != 0
# --- Command rendering ---
def test_format_command_renders_make_target() -> None:
item = TestCatalogItem(
id="make:test-cov",
kind="make_target",
display_name="Coverage Suite",
description="Run coverage.",
command=("make", "test-cov"),
tags=("ci-safe",),
requirements=TestRequirement(),
)
assert format_command(item) == "make test-cov"
def test_format_command_renders_opensre_subcommand() -> None:
item = TestCatalogItem(
id="synthetic:001-replication-lag",
kind="cli_command",
display_name="001-replication-lag",
description="Synthetic scenario.",
command=("opensre", "tests", "synthetic", "--scenario", "001-replication-lag"),
tags=("synthetic",),
requirements=TestRequirement(env_vars=("ANTHROPIC_API_KEY",)),
)
assert "opensre" in format_command(item)
assert "001-replication-lag" in format_command(item)
def test_format_command_renders_openclaw_synthetic_subcommand() -> None:
item = TestCatalogItem(
id="openclaw-synthetic:gateway_process_terminated_missing_tls_key",
kind="cli_command",
display_name="OpenClaw synthetic scenario",
description="Synthetic OpenClaw scenario.",
command=(
"opensre",
"tests",
"openclaw-synthetic",
"--scenario",
"gateway_process_terminated_missing_tls_key",
),
tags=("synthetic", "openclaw", "rca"),
requirements=TestRequirement(notes=("Configured LLM provider",)),
)
assert "openclaw-synthetic" in format_command(item)
assert "gateway_process_terminated_missing_tls_key" in format_command(item)
# --- runner.run_catalog_items non-runnable skip ---
def test_run_catalog_items_skips_non_runnable_and_prints_message(
capsys: pytest.CaptureFixture[str],
) -> None:
no_cmd = TestCatalogItem(
id="suite:empty",
kind="suite",
display_name="Empty Suite",
description="No command.",
command=(),
tags=(),
requirements=TestRequirement(),
)
exit_code = run_catalog_items([no_cmd])
assert exit_code == 0
captured = capsys.readouterr()
assert "suite:empty" in captured.err
assert "Skipping" in captured.err
def test_run_catalog_item_prints_openclaw_preflight_before_execution(
monkeypatch, capsys: pytest.CaptureFixture[str]
) -> None:
item = TestCatalogItem(
id="rca:openclaw_gateway_crashed",
kind="rca_file",
display_name="OpenClaw Gateway Crashed",
description="Run a bundled markdown RCA alert fixture.",
command=("python", "-c", "raise SystemExit(0)"),
tags=("rca", "fixture", "openclaw"),
requirements=TestRequirement(),
)
monkeypatch.setattr(
"surfaces.cli.tests.runner.get_preflight_messages",
lambda _item: ("OpenClaw preflight: unavailable.",),
)
exit_code = run_catalog_item(item)
assert exit_code == 0
captured = capsys.readouterr()
assert "OpenClaw preflight: unavailable." in captured.err
# ---------------------------------------------------------------------------
# Bundled-binary degradation for ``opensre tests synthetic`` (regression #1078)
#
# the release PyInstaller build excludes the ``tests`` tree
# bundles, so ``from tests.synthetic.rds_postgres.run_suite import main``
# raises ``ModuleNotFoundError`` in a packaged binary. Surface a clean
# ``OpenSREError`` instead of a raw traceback so users know to run from a
# source checkout.
# ---------------------------------------------------------------------------
def test_tests_synthetic_clean_error_when_data_dir_missing(tmp_path: Path) -> None:
"""Real bundled-binary failure mode (per live PyInstaller verification):
the ``tests.synthetic.rds_postgres`` Python package is bundled
transitively but the per-scenario data directories are absent. Pre-check
on the data dir must surface a structured error before the import fires."""
runner = CliRunner()
# Point SYNTHETIC_SCENARIOS_DIR at a path that doesn't exist so the
# pre-check in ``run_synthetic_suite`` short-circuits to OpenSREError.
missing = tmp_path / "missing-rds-postgres"
with unittest.mock.patch("surfaces.cli.tests.discover.SYNTHETIC_SCENARIOS_DIR", missing):
result = runner.invoke(cli, ["tests", "synthetic", "--scenario", "001-replication-lag"])
output = result.output or ""
assert result.exit_code == 1, f"unexpected exit code {result.exit_code}; output={output!r}"
# Pin the contractual message — these strings are user-facing and a
# silent rewording would be a regression for support docs / scripts.
assert "synthetic RDS PostgreSQL suite is not available" in output
assert "pip install -e ." in output
# No raw traceback or stdlib exception name reaches the user.
assert "FileNotFoundError" not in output
assert "ModuleNotFoundError" not in output
assert "Traceback" not in output
def test_tests_synthetic_clean_error_when_module_not_bundled(tmp_path: Path) -> None:
"""Adjacent failure mode: the synthetic Python package is missing
entirely. The narrowed ``ModuleNotFoundError`` catch must convert it
into the same structured error."""
runner = CliRunner()
real_import = __import__
def _fail_synthetic_import(name: str, *args: object, **kwargs: object) -> object:
if name.startswith("tests.synthetic.rds_postgres"):
raise ModuleNotFoundError(f"No module named {name!r}", name=name)
return real_import(name, *args, **kwargs) # type: ignore[arg-type]
# Make the data-dir pre-check pass so the import path is what fails.
scenarios_dir = tmp_path / "rds_postgres"
(scenarios_dir / "001-replication-lag").mkdir(parents=True)
with (
unittest.mock.patch("surfaces.cli.tests.discover.SYNTHETIC_SCENARIOS_DIR", scenarios_dir),
unittest.mock.patch("builtins.__import__", side_effect=_fail_synthetic_import),
):
result = runner.invoke(cli, ["tests", "synthetic", "--scenario", "001-replication-lag"])
output = result.output or ""
assert result.exit_code == 1, f"unexpected exit code {result.exit_code}; output={output!r}"
assert "synthetic RDS PostgreSQL suite is not available" in output
assert "ModuleNotFoundError" not in output
assert "Traceback" not in output
def test_tests_synthetic_unrelated_module_not_found_propagates(tmp_path: Path) -> None:
"""Narrow-catch contract: if the synthetic suite *is* available but a
transitive dep (e.g. ``psycopg``) is missing, the user must see the
real cause — not a misleading "not bundled" message."""
runner = CliRunner()
real_import = __import__
def _fail_unrelated_import(name: str, *args: object, **kwargs: object) -> object:
# Synthetic module's run_suite fails because of a missing transitive
# dep (``psycopg``), not because the synthetic package is absent.
if name == "tests.synthetic.rds_postgres.run_suite":
raise ModuleNotFoundError("No module named 'psycopg'", name="psycopg")
return real_import(name, *args, **kwargs) # type: ignore[arg-type]
scenarios_dir = tmp_path / "rds_postgres"
(scenarios_dir / "001-replication-lag").mkdir(parents=True)
with (
unittest.mock.patch("surfaces.cli.tests.discover.SYNTHETIC_SCENARIOS_DIR", scenarios_dir),
unittest.mock.patch("builtins.__import__", side_effect=_fail_unrelated_import),
):
result = runner.invoke(cli, ["tests", "synthetic", "--scenario", "001-replication-lag"])
output = result.output or ""
# The misleading "not bundled" message MUST NOT be shown — the user
# needs to see the real missing-dep cause so they can fix it.
assert "synthetic RDS PostgreSQL suite is not available" not in output