From 3b77fd6967b177ebb32045cf13fc65deedae2d12 Mon Sep 17 00:00:00 2001 From: leiwilson <305842734+leiwilson@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:09:08 +0100 Subject: [PATCH 1/2] feat(cli): add ids command Co-authored-by: Cursor Agent --- scenarios/cli.py | 7 +++++++ tests/test_cli.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/scenarios/cli.py b/scenarios/cli.py index 123227c..1925c56 100644 --- a/scenarios/cli.py +++ b/scenarios/cli.py @@ -59,6 +59,8 @@ def build_parser() -> argparse.ArgumentParser: ) subparsers.add_parser("version", help="Print package version") + + subparsers.add_parser("ids", help="Print known scenario ids") return parser @@ -150,6 +152,11 @@ def main(argv: list[str] | None = None) -> int: print("0.1.0") return 0 + if args.command == "ids": + for scenario_id in known_scenario_ids(): + print(scenario_id) + return 0 + return 1 diff --git a/tests/test_cli.py b/tests/test_cli.py index 3d50783..d3381ec 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -191,6 +191,14 @@ def test_version_prints_package_version(self): self.assertEqual(code, 0) self.assertEqual(buffer.getvalue().strip(), "0.1.0") + def test_ids_prints_one_id_per_line(self): + buffer = io.StringIO() + with redirect_stdout(buffer): + code = main(["ids"]) + self.assertEqual(code, 0) + lines = [line for line in buffer.getvalue().splitlines() if line.strip()] + self.assertEqual(lines, list(known_scenario_ids())) + From 59da4249a126f39f4b1bd6f12f865f02b429642a Mon Sep 17 00:00:00 2001 From: leiwilson <305842734+leiwilson@users.noreply.github.com> Date: Wed, 12 Aug 2026 07:11:25 +0100 Subject: [PATCH 2/2] feat(clients): add CircuitBreaker.reset Co-authored-by: Cursor Agent --- clients/circuit_breaker.py | 5 +++++ tests/test_circuit_breaker.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/clients/circuit_breaker.py b/clients/circuit_breaker.py index 4501d63..2c16e36 100644 --- a/clients/circuit_breaker.py +++ b/clients/circuit_breaker.py @@ -41,6 +41,11 @@ def state(self) -> str: return "half_open" return "open" + def reset(self) -> None: + """Clear failure count and close the circuit.""" + self.failures = 0 + self.opened_at = None + @property def is_open(self) -> bool: if self.opened_at is None: diff --git a/tests/test_circuit_breaker.py b/tests/test_circuit_breaker.py index bfef340..6e4fbfa 100644 --- a/tests/test_circuit_breaker.py +++ b/tests/test_circuit_breaker.py @@ -125,6 +125,31 @@ def boom(): self.assertEqual(breaker.call(lambda: {"ok": True})["ok"], True) self.assertEqual(breaker.state, "closed") + def test_reset_clears_failures_and_opened_at(self): + clock = FakeClock() + breaker = CircuitBreaker( + failure_threshold=1, + recovery_timeout=1.0, + watch=(TransientError,), + clock=clock, + ) + + def boom(): + raise TransientError("x") + + with self.assertRaises(TransientError): + breaker.call(boom) + self.assertEqual(breaker.failures, 1) + self.assertIsNotNone(breaker.opened_at) + self.assertEqual(breaker.state, "open") + self.assertTrue(breaker.is_open) + + breaker.reset() + self.assertEqual(breaker.failures, 0) + self.assertIsNone(breaker.opened_at) + self.assertEqual(breaker.state, "closed") + self.assertFalse(breaker.is_open) +