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
257 lines
10 KiB
Python
257 lines
10 KiB
Python
"""Tests for Session state."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import config.constants as const_module
|
|
from config.constants.prompts import SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST
|
|
from platform.common.task_registry import TaskRegistry
|
|
from platform.common.task_types import TaskKind
|
|
from surfaces.interactive_shell.session import (
|
|
Session,
|
|
)
|
|
from surfaces.interactive_shell.session.session import _scenario_id_from_synthetic_label
|
|
|
|
|
|
class TestSession:
|
|
def test_defaults(self) -> None:
|
|
session = Session()
|
|
assert session.history == []
|
|
assert session.last_state is None
|
|
assert session.accumulated_context == {}
|
|
assert session.terminal.trust_mode is False
|
|
assert session.task_registry.list_recent() == []
|
|
assert session.terminal.metrics.turn_count == 0
|
|
assert session.terminal.metrics.fallback_count == 0
|
|
assert session.terminal.metrics.ctrl_c_intervention_count == 0
|
|
assert session.terminal.metrics.correction_intervention_count == 0
|
|
assert session.terminal.pending_prompt_default is None
|
|
assert session.last_synthetic_observation_path is None
|
|
|
|
def test_take_pending_prompt_default_returns_and_clears(self) -> None:
|
|
session = Session()
|
|
session.terminal.pending_prompt_default = "why did it fail?"
|
|
assert session.terminal.pop_pending_prompt_default() == "why did it fail?"
|
|
assert session.terminal.pending_prompt_default is None
|
|
assert session.terminal.pop_pending_prompt_default() == ""
|
|
|
|
def test_clear_resets_pending_prompt_default(self) -> None:
|
|
session = Session()
|
|
session.terminal.pending_prompt_default = "why did it fail?"
|
|
session.clear()
|
|
assert session.terminal.pending_prompt_default is None
|
|
|
|
def test_queue_auto_command_sets_pending_and_notifies(self) -> None:
|
|
session = Session()
|
|
calls: list[bool] = []
|
|
session.terminal.prompt_refresh_fn = lambda: calls.append(True)
|
|
session.terminal.set_auto_command("/integrations setup sentry")
|
|
assert session.terminal.pending_prompt_default == "/integrations setup sentry"
|
|
assert session.terminal.pending_prompt_autosubmit is True
|
|
assert calls == [True]
|
|
|
|
def test_take_pending_autosubmit_returns_and_clears(self) -> None:
|
|
session = Session()
|
|
session.terminal.pending_prompt_autosubmit = True
|
|
assert session.terminal.pop_pending_autosubmit() is True
|
|
assert session.terminal.pending_prompt_autosubmit is False
|
|
assert session.terminal.pop_pending_autosubmit() is False
|
|
|
|
def test_clear_resets_pending_autosubmit(self) -> None:
|
|
session = Session()
|
|
session.terminal.set_auto_command("/integrations setup sentry")
|
|
session.clear()
|
|
assert session.terminal.pending_prompt_autosubmit is False
|
|
assert session.terminal.pending_prompt_default is None
|
|
|
|
def test_scenario_id_from_synthetic_label(self) -> None:
|
|
assert (
|
|
_scenario_id_from_synthetic_label(
|
|
"opensre tests synthetic --scenario 001-replication-lag"
|
|
)
|
|
== "001-replication-lag"
|
|
)
|
|
assert _scenario_id_from_synthetic_label("rds_postgres:001-replication-lag") == (
|
|
"001-replication-lag"
|
|
)
|
|
assert _scenario_id_from_synthetic_label("opensre tests synthetic --scenario ./evil") == ""
|
|
assert _scenario_id_from_synthetic_label("rds_postgres:not-a-scenario") == ""
|
|
|
|
def test_suggest_synthetic_failure_follow_up_sets_pending(self) -> None:
|
|
session = Session()
|
|
session.suggest_synthetic_failure_follow_up(
|
|
label="opensre tests synthetic --scenario 001-replication-lag",
|
|
)
|
|
assert (
|
|
session.terminal.pending_prompt_default == SUGGESTED_PROMPT_AFTER_FAILED_SYNTHETIC_TEST
|
|
)
|
|
|
|
def test_record_appends_entry(self) -> None:
|
|
session = Session()
|
|
session.record("alert", "cpu high")
|
|
session.record("slash", "/status", ok=True)
|
|
session.record("alert", "bad one", ok=False)
|
|
assert len(session.history) == 3
|
|
assert session.history[-1]["type"] == "alert"
|
|
assert session.history[-1]["ok"] is False
|
|
|
|
def test_mark_latest_updates_most_recent_matching_kind(self) -> None:
|
|
session = Session()
|
|
session.record("slash", "/investigate missing.json")
|
|
session.record("alert", "missing.json", ok=False)
|
|
|
|
session.mark_latest(ok=False, kind="slash")
|
|
|
|
assert session.history[0]["ok"] is False
|
|
assert session.history[1]["ok"] is False
|
|
|
|
def test_clear_preserves_trust_mode(self) -> None:
|
|
session = Session()
|
|
session.terminal.trust_mode = True
|
|
session.terminal.background_notification_preferences.set_channels(["email"])
|
|
session.accumulated_context["service"] = "api"
|
|
session.record("alert", "something")
|
|
session.last_state = {"foo": "bar"}
|
|
session.agent.messages.append(("user", "hey"))
|
|
session.terminal.metrics.record_intervention("ctrl_c")
|
|
session.terminal.metrics.record_intervention("correction")
|
|
|
|
assert session.terminal.history_generation == 0
|
|
session.clear()
|
|
assert session.terminal.history_generation == 1
|
|
|
|
assert session.history == []
|
|
assert session.last_state is None
|
|
assert session.accumulated_context == {}
|
|
assert session.agent.messages == []
|
|
assert session.task_registry.list_recent() == []
|
|
assert session.terminal.metrics.ctrl_c_intervention_count == 0
|
|
assert session.terminal.metrics.correction_intervention_count == 0
|
|
assert session.terminal.background_notification_preferences.channels == ("email",)
|
|
assert session.terminal.trust_mode is True # preserved intentionally
|
|
|
|
def test_clear_keeps_persisted_task_history_file(
|
|
self,
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
session = Session()
|
|
monkeypatch.setattr(const_module, "OPENSRE_HOME_DIR", tmp_path)
|
|
session.task_registry = TaskRegistry.persistent()
|
|
task = session.task_registry.create(
|
|
TaskKind.SYNTHETIC_TEST, command="opensre tests synthetic"
|
|
)
|
|
task.mark_running()
|
|
|
|
session.clear()
|
|
|
|
reloaded = TaskRegistry.persistent()
|
|
loaded = reloaded.get(task.task_id)
|
|
assert loaded is not None
|
|
assert loaded.task_id == task.task_id
|
|
|
|
def test_accumulate_from_state_extracts_known_keys(self) -> None:
|
|
session = Session()
|
|
session.accumulate_from_state(
|
|
{
|
|
"service": "orders-api",
|
|
"pipeline_name": "events_fact",
|
|
"cluster_name": "prod-us-east",
|
|
"region": "us-east-1",
|
|
"environment": "production",
|
|
"root_cause": "disk full", # not accumulated
|
|
"evidence": {"ev-1": "x"}, # not accumulated
|
|
}
|
|
)
|
|
assert session.accumulated_context == {
|
|
"service": "orders-api",
|
|
"pipeline_name": "events_fact",
|
|
"cluster_name": "prod-us-east",
|
|
"region": "us-east-1",
|
|
"environment": "production",
|
|
}
|
|
|
|
def test_accumulate_from_state_skips_empty_and_none(self) -> None:
|
|
session = Session()
|
|
session.accumulate_from_state(
|
|
{
|
|
"service": "",
|
|
"cluster_name": None,
|
|
"region": "us-east-1",
|
|
}
|
|
)
|
|
assert session.accumulated_context == {"region": "us-east-1"}
|
|
|
|
def test_accumulate_from_state_merges_across_calls(self) -> None:
|
|
"""Subsequent investigations fill in context the earlier one didn't have."""
|
|
session = Session()
|
|
session.accumulate_from_state({"service": "orders-api"})
|
|
session.accumulate_from_state({"cluster_name": "prod-us-east"})
|
|
assert session.accumulated_context == {
|
|
"service": "orders-api",
|
|
"cluster_name": "prod-us-east",
|
|
}
|
|
|
|
def test_accumulate_from_state_handles_none_and_empty_state(self) -> None:
|
|
session = Session()
|
|
session.accumulate_from_state(None)
|
|
session.accumulate_from_state({})
|
|
assert session.accumulated_context == {}
|
|
|
|
def test_record_terminal_turn_updates_aggregates(self) -> None:
|
|
session = Session()
|
|
|
|
first = session.terminal.metrics.record_turn(
|
|
executed_count=2,
|
|
executed_success_count=1,
|
|
fallback_to_llm=True,
|
|
)
|
|
second = session.terminal.metrics.record_turn(
|
|
executed_count=1,
|
|
executed_success_count=1,
|
|
fallback_to_llm=False,
|
|
)
|
|
|
|
assert first.turn_index == 1
|
|
assert first.fallback_count == 1
|
|
assert first.action_success_percent == 50.0
|
|
assert first.fallback_rate_percent == 100.0
|
|
|
|
assert second.turn_index == 2
|
|
assert second.fallback_count == 1
|
|
assert round(second.action_success_percent, 2) == 66.67
|
|
assert second.fallback_rate_percent == 50.0
|
|
|
|
def test_record_intervention_increments_per_kind(self) -> None:
|
|
session = Session()
|
|
|
|
session.terminal.metrics.record_intervention("ctrl_c")
|
|
session.terminal.metrics.record_intervention("ctrl_c")
|
|
session.terminal.metrics.record_intervention("correction")
|
|
|
|
assert session.terminal.metrics.ctrl_c_intervention_count == 2
|
|
assert session.terminal.metrics.correction_intervention_count == 1
|
|
|
|
def test_record_intervention_kinds_are_independent(self) -> None:
|
|
"""Incrementing one kind does not touch the other."""
|
|
session = Session()
|
|
|
|
session.terminal.metrics.record_intervention("correction")
|
|
|
|
assert session.terminal.metrics.ctrl_c_intervention_count == 0
|
|
assert session.terminal.metrics.correction_intervention_count == 1
|
|
|
|
def test_fresh_session_starts_with_zero_intervention_counts(self) -> None:
|
|
"""A new Session does not inherit any prior session's counters."""
|
|
first = Session()
|
|
first.terminal.metrics.record_intervention("ctrl_c")
|
|
first.terminal.metrics.record_intervention("correction")
|
|
|
|
second = Session()
|
|
|
|
assert second.terminal.metrics.ctrl_c_intervention_count == 0
|
|
assert second.terminal.metrics.correction_intervention_count == 0
|