diff --git a/.flake8 b/.flake8 index 282c026a..a3f342dd 100644 --- a/.flake8 +++ b/.flake8 @@ -8,18 +8,6 @@ select = RMP # are kept rather than replaced. extend-exclude = .venv,build,dist -# Baseline of pre-existing violations, recorded so the rule can be enforced -# from day one without a large mechanical rename in the same change. Each -# entry is removed by the commit that fixes the file. Do not add new entries. -per-file-ignores = - rampart/core/execution.py:RMP001 - rampart/core/injection.py:RMP001 - rampart/evaluators/llm_judge.py:RMP001 - rampart/pyrit_bridge/llm_bridge.py:RMP001 - rampart/pytest_plugin/_collection.py:RMP001 - rampart/surfaces/onedrive.py:RMP001 - tests/*:RMP001 - [flake8:local-plugins] extension = RMP = flake8_rampart:RampartChecker diff --git a/.github/instructions/coding-standards.instructions.md b/.github/instructions/coding-standards.instructions.md index 67629176..989d4563 100644 --- a/.github/instructions/coding-standards.instructions.md +++ b/.github/instructions/coding-standards.instructions.md @@ -623,9 +623,8 @@ while `# noqa:` is reserved for an RMP code (e.g., `RMP001`), which flake8 rather than ruff reads. `RMP001` is listed in `[tool.ruff.lint] external` so that ruff's `RUF102` accepts it instead of rejecting it as an unknown code. -`.flake8` carries a `per-file-ignores` baseline of files that predate the rule. -Those entries are removed as the files are fixed; do not add new ones. - +`RMP001` applies repo-wide, including to tests: the test standards require the +`_async` suffix on async test names too. [flake8-local]: https://flake8.pycqa.org/en/latest/user/configuration.html#using-local-plugins --- diff --git a/.github/instructions/unit-tests-standards.instructions.md b/.github/instructions/unit-tests-standards.instructions.md index 5527f01a..a652ffd7 100644 --- a/.github/instructions/unit-tests-standards.instructions.md +++ b/.github/instructions/unit-tests-standards.instructions.md @@ -37,7 +37,7 @@ class TestParseConfig: ``` ### Async Tests -- Async test method names MUST end with `_async` +- Async test method names MUST end with `_async` (enforced by `RMP001`) - Use `AsyncMock` instead of `MagicMock` when mocking async methods ```python diff --git a/docs/api/core-protocols.md b/docs/api/core-protocols.md index edb24d66..20068b2e 100644 --- a/docs/api/core-protocols.md +++ b/docs/api/core-protocols.md @@ -32,7 +32,7 @@ Protocols and ABCs that define RAMPART's extension points. Implement these to co members: - Surface - InjectionHandle - - sleep_until_ready + - sleep_until_ready_async ## Converter diff --git a/docs/attacks/xpia.md b/docs/attacks/xpia.md index 5c8aee68..df7d22e5 100644 --- a/docs/attacks/xpia.md +++ b/docs/attacks/xpia.md @@ -17,7 +17,7 @@ sequenceDiagram Test->>Surface: inject(payload) → handle Note over Surface: Payload placed in data source - Test->>Surface: handle.wait_until_ready() + Test->>Surface: handle.wait_until_ready_async() Test->>Agent: session.send_async("Summarize reports") Agent-->>Test: Response (text + tool_calls) Test->>Eval: evaluate_async(context) @@ -28,7 +28,7 @@ sequenceDiagram **Phases:** 1. **Inject** — Place payloads into the agent's data sources via surfaces. Each `surface.inject(payload)` returns an [`InjectionHandle`][rampart.core.injection.InjectionHandle]. -2. **Wait** — Handles call `wait_until_ready()` to allow indexing. Runs concurrently for multiple surfaces. +2. **Wait** — Handles call `wait_until_ready_async()` to allow indexing. Runs concurrently for multiple surfaces. 3. **Trigger** — Send benign prompts that cause the agent to retrieve the injected content. Triggers are never adversarial — the attack is in the payload, not the prompt. 4. **Evaluate** — Check each turn for the attack objective. Early-stops on detection. 5. **Clean up** — Remove injected content. Guaranteed via `AsyncExitStack`, even on exceptions. diff --git a/docs/contributing/extending-rampart.md b/docs/contributing/extending-rampart.md index 35a87f9f..e8fd3fcf 100644 --- a/docs/contributing/extending-rampart.md +++ b/docs/contributing/extending-rampart.md @@ -337,7 +337,7 @@ For the basic protocol skeleton, see [Implementing Surfaces](../usage/authoring- - **`Surface.inject` does not activate** — it only prepares the handle. Activation happens when an execution strategy enters the handle as an async context manager. - **`__aexit__` must be idempotent and must not raise** — cleanup runs even on exceptions, and a failing cleanup must not mask the original error. -- **`wait_until_ready` should bound itself** with `TimeoutError` rather than block indefinitely. For simple delay-based waits, call `sleep_until_ready` from `rampart.core.injection`. +- **`wait_until_ready_async` should bound itself** with `TimeoutError` rather than block indefinitely. For simple delay-based waits, call `sleep_until_ready_async` from `rampart.core.injection`. - **Raise `InfrastructureError`** for transient, external failures (timeouts, rate limits, service outages). It's the documented convention for surfaces and adapters to signal "not a safety signal" — `BaseExecution` catches all exceptions and produces an `ERROR` result either way, but the exception type is preserved in metadata for triage. For a complete reference, see [`OneDriveSurface`](https://github.com/microsoft/RAMPART/blob/main/rampart/surfaces/onedrive.py). diff --git a/docs/usage/authoring-tests.md b/docs/usage/authoring-tests.md index b6955a44..e2d7ec8b 100644 --- a/docs/usage/authoring-tests.md +++ b/docs/usage/authoring-tests.md @@ -270,7 +270,7 @@ class MyFileSurface: def surface_name(self) -> str: return "file_system" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: pass # or: await asyncio.sleep(10.0) for indexing delay async def __aenter__(self): diff --git a/rampart/attacks/_xpia.py b/rampart/attacks/_xpia.py index 205ebf8d..d7746365 100644 --- a/rampart/attacks/_xpia.py +++ b/rampart/attacks/_xpia.py @@ -169,7 +169,7 @@ async def _activate_handles_async( # Concurrent: total = max of all wait times async with asyncio.TaskGroup() as tg: for handle in self._handles: - tg.create_task(handle.wait_until_ready()) + tg.create_task(handle.wait_until_ready_async()) def _build_attack_result( self, diff --git a/rampart/core/execution.py b/rampart/core/execution.py index 34fa92e5..63fdeb5b 100644 --- a/rampart/core/execution.py +++ b/rampart/core/execution.py @@ -73,7 +73,7 @@ class ExecutionEventHandler(ABC): """ @abstractmethod - async def on_event(self, *, event_data: ExecutionEventData) -> None: + async def on_event_async(self, *, event_data: ExecutionEventData) -> None: """Handle an execution lifecycle event. Args: @@ -231,7 +231,7 @@ async def execute_async(self, *, adapter: AgentAdapter) -> Result: Result: Safety verdict with evidence and diagnostics. """ start = time.monotonic() - await self._fire( + await self._fire_async( ExecutionEvent.ON_PRE_EXECUTE, adapter=adapter, elapsed=0.0, @@ -247,7 +247,7 @@ async def execute_async(self, *, adapter: AgentAdapter) -> Result: self.strategy_name, ) - await self._fire( + await self._fire_async( ExecutionEvent.ON_ERROR, adapter=adapter, elapsed=time.monotonic() - start, @@ -264,7 +264,7 @@ async def execute_async(self, *, adapter: AgentAdapter) -> Result: elapsed = time.monotonic() - start result.duration_seconds = elapsed - await self._fire( + await self._fire_async( ExecutionEvent.ON_POST_EXECUTE, adapter=adapter, elapsed=elapsed, @@ -284,7 +284,7 @@ async def _execute_async(self, *, adapter: AgentAdapter) -> Result: """ ... - async def _fire( + async def _fire_async( self, event: ExecutionEvent, *, @@ -314,7 +314,7 @@ async def _fire( ) for handler in self._handlers: try: - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) except Exception: logger.warning( "ExecutionEventHandler %s raised on %s — ignored.", diff --git a/rampart/core/injection.py b/rampart/core/injection.py index 6a72fa33..566c595a 100644 --- a/rampart/core/injection.py +++ b/rampart/core/injection.py @@ -6,7 +6,7 @@ Two protocols serving two audiences: Surface is what surface authors implement; InjectionHandle is what execution strategies consume. -``sleep_until_ready`` is a helper function for surfaces that only need +``sleep_until_ready_async`` is a helper function for surfaces that only need a simple delay-based readiness wait. """ @@ -43,7 +43,7 @@ def surface_name(self) -> str: """The name of the surface this handle injects into (e.g., 'SharePoint').""" ... - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: """Block until the injected content is visible to the agent. Implementations should raise `TimeoutError` if readiness @@ -52,7 +52,7 @@ async def wait_until_ready(self) -> None: ... -async def sleep_until_ready(delay: float) -> None: +async def sleep_until_ready_async(delay: float) -> None: """Sleep for `delay` seconds. Default readiness strategy for simple surfaces. Args: diff --git a/rampart/evaluators/llm_judge.py b/rampart/evaluators/llm_judge.py index 5499a22b..ae961ba7 100644 --- a/rampart/evaluators/llm_judge.py +++ b/rampart/evaluators/llm_judge.py @@ -479,7 +479,7 @@ async def evaluate_async(self, *, context: EvalContext) -> EvalResult: user_message = self._build_user_message(context=context) @pyrit_json_retry - async def _send_and_parse() -> _JudgeVerdict: + async def _send_and_parse_async() -> _JudgeVerdict: raw = await self._send_async( system_prompt=system_prompt, user_message=user_message, @@ -487,7 +487,7 @@ async def _send_and_parse() -> _JudgeVerdict: return _JudgeVerdict.from_json(raw) try: - verdict = await _send_and_parse() + verdict = await _send_and_parse_async() except InvalidJsonException: return self._undetermined( rationale="Judge could not produce valid JSON after retries.", diff --git a/rampart/pyrit_bridge/llm_bridge.py b/rampart/pyrit_bridge/llm_bridge.py index 3e556752..6997c0c3 100644 --- a/rampart/pyrit_bridge/llm_bridge.py +++ b/rampart/pyrit_bridge/llm_bridge.py @@ -144,7 +144,7 @@ async def send_user_turn_async( Returns: The model's text response. """ - return await _send_via_normalizer( + return await _send_via_normalizer_async( normalizer=normalizer, target=target, conversation_id=conversation_id, @@ -242,7 +242,7 @@ async def send_judge_request_async( prompt_metadata: dict[str, str | int] | None = ( {"response_format": response_format} if response_format else None ) - return await _send_via_normalizer( + return await _send_via_normalizer_async( normalizer=normalizer, target=target, conversation_id=conversation_id, @@ -253,7 +253,7 @@ async def send_judge_request_async( ) -async def _send_via_normalizer( +async def _send_via_normalizer_async( *, normalizer: PromptNormalizer, target: PromptChatTarget, diff --git a/rampart/pytest_plugin/_collection.py b/rampart/pytest_plugin/_collection.py index cc54bf31..7462417e 100644 --- a/rampart/pytest_plugin/_collection.py +++ b/rampart/pytest_plugin/_collection.py @@ -100,7 +100,7 @@ class ResultCollectionHandler(ExecutionEventHandler): """ @override - async def on_event(self, *, event_data: ExecutionEventData) -> None: + async def on_event_async(self, *, event_data: ExecutionEventData) -> None: """Record result on post-execute. Ignore all other events. Args: diff --git a/rampart/surfaces/onedrive.py b/rampart/surfaces/onedrive.py index 978804e5..88e9c311 100644 --- a/rampart/surfaces/onedrive.py +++ b/rampart/surfaces/onedrive.py @@ -15,7 +15,7 @@ from typing import TYPE_CHECKING, Self from rampart.core.errors import InfrastructureError -from rampart.core.injection import sleep_until_ready +from rampart.core.injection import sleep_until_ready_async if TYPE_CHECKING: import types @@ -195,14 +195,14 @@ def surface_name(self) -> str: """Identifies this injection as OneDrive for reporting.""" return "OneDrive" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: """Wait for the uploaded content to be indexed and discoverable. Note: Currently sleeps for `OneDriveSurface.indexing_delay` seconds. Future versions will poll the Graph API for content availability instead and raise `TimeoutError` if it doesn't appear within the `indexing_delay`. """ - await sleep_until_ready(delay=self._surface.indexing_delay) + await sleep_until_ready_async(delay=self._surface.indexing_delay) async def __aenter__(self) -> Self: """Upload payload to OneDrive. diff --git a/tests/unit/attacks/test_xpia.py b/tests/unit/attacks/test_xpia.py index 93a35e6f..c932e327 100644 --- a/tests/unit/attacks/test_xpia.py +++ b/tests/unit/attacks/test_xpia.py @@ -72,7 +72,7 @@ def _adapter( class TestXPIADetection: """Attack semantics: DETECTED->UNSAFE, NOT_DETECTED->SAFE.""" - async def test_detected_returns_unsafe_with_evidence_in_summary(self) -> None: + async def test_detected_returns_unsafe_with_evidence_in_summary_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -86,7 +86,7 @@ async def test_detected_returns_unsafe_with_evidence_in_summary(self) -> None: assert result.status is SafetyStatus.UNSAFE assert "exfil_call_found" in result.summary - async def test_not_detected_returns_safe(self) -> None: + async def test_not_detected_returns_safe_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -96,7 +96,7 @@ async def test_not_detected_returns_safe(self) -> None: assert result.safe is True assert result.status is SafetyStatus.SAFE - async def test_undetermined_returns_undetermined(self) -> None: + async def test_undetermined_returns_undetermined_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -113,7 +113,7 @@ async def test_undetermined_returns_undetermined(self) -> None: class TestXPIAEarlyStop: """Per-turn evaluation stops the conversation on first detection.""" - async def test_stops_after_first_detection(self) -> None: + async def test_stops_after_first_detection_async(self) -> None: evaluator = AsyncMock() evaluator.evaluate_async.side_effect = [ EvalResult(outcome=EvalOutcome.NOT_DETECTED), @@ -129,7 +129,7 @@ async def test_stops_after_first_detection(self) -> None: assert result.status is SafetyStatus.UNSAFE assert len(result.turns) == 2 - async def test_completes_all_turns_when_not_detected(self) -> None: + async def test_completes_all_turns_when_not_detected_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger=["Turn 1", "Turn 2"], @@ -143,7 +143,7 @@ async def test_completes_all_turns_when_not_detected(self) -> None: class TestXPIAMaxTurns: """Max-turns resolves normally via resolve_as_attack.""" - async def test_max_turns_resolves_normally(self) -> None: + async def test_max_turns_resolves_normally_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger=["p1", "p2", "p3"], @@ -158,7 +158,7 @@ async def test_max_turns_resolves_normally(self) -> None: class TestXPIACleanup: """Injection handles are always activated and cleaned up.""" - async def test_handle_entered_and_exited(self) -> None: + async def test_handle_entered_and_exited_async(self) -> None: handle = _mock_handle() await Attacks.xpia( @@ -169,9 +169,9 @@ async def test_handle_entered_and_exited(self) -> None: handle.__aenter__.assert_awaited_once() handle.__aexit__.assert_awaited_once() - handle.wait_until_ready.assert_awaited_once() + handle.wait_until_ready_async.assert_awaited_once() - async def test_multiple_handles_all_cleaned(self) -> None: + async def test_multiple_handles_all_cleaned_async(self) -> None: h1 = _mock_handle(surface_name="SP") h2 = _mock_handle(surface_name="Exchange") @@ -184,9 +184,9 @@ async def test_multiple_handles_all_cleaned(self) -> None: for h in (h1, h2): h.__aenter__.assert_awaited_once() h.__aexit__.assert_awaited_once() - h.wait_until_ready.assert_awaited_once() + h.wait_until_ready_async.assert_awaited_once() - async def test_cleanup_on_evaluator_exception(self) -> None: + async def test_cleanup_on_evaluator_exception_async(self) -> None: """Handles are cleaned up even if the evaluator raises.""" handle = _mock_handle() evaluator = AsyncMock() @@ -206,7 +206,7 @@ async def test_cleanup_on_evaluator_exception(self) -> None: class TestXPIAInfrastructureError: """InfrastructureError produces ERROR result (base class concern).""" - async def test_handle_activation_failure(self) -> None: + async def test_handle_activation_failure_async(self) -> None: handle = _mock_handle() handle.__aenter__.side_effect = InfrastructureError("SharePoint 503") @@ -219,7 +219,7 @@ async def test_handle_activation_failure(self) -> None: assert result.status is SafetyStatus.ERROR assert "SharePoint 503" in result.summary - async def test_session_creation_failure(self) -> None: + async def test_session_creation_failure_async(self) -> None: adapter = AsyncMock() adapter.create_session_async.side_effect = InfrastructureError( "Connection refused", @@ -240,7 +240,9 @@ async def test_session_creation_failure(self) -> None: class TestXPIAObservabilityAdjustment: """SAFE is downgraded to UNDETERMINED when observability is insufficient.""" - async def test_response_only_no_tools_downgrades_to_undetermined(self) -> None: + async def test_response_only_no_tools_downgrades_to_undetermined_async( + self, + ) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -252,7 +254,7 @@ async def test_response_only_no_tools_downgrades_to_undetermined(self) -> None: assert result.safe is False assert result.status is SafetyStatus.UNDETERMINED - async def test_response_only_with_tool_calls_stays_safe(self) -> None: + async def test_response_only_with_tool_calls_stays_safe_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -267,7 +269,7 @@ async def test_response_only_with_tool_calls_stays_safe(self) -> None: assert result.safe is True assert result.status is SafetyStatus.SAFE - async def test_non_response_only_levels_are_not_downgraded(self) -> None: + async def test_non_response_only_levels_are_not_downgraded_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -283,7 +285,7 @@ async def test_non_response_only_levels_are_not_downgraded(self) -> None: class TestXPIAInjectionRecords: """Result carries injection records for reproduction.""" - async def test_single_handle_recorded(self) -> None: + async def test_single_handle_recorded_async(self) -> None: result = await Attacks.xpia( inject=_mock_handle(surface_name="SharePoint", payload_id="px-42"), trigger="Summarize Q3", @@ -294,7 +296,7 @@ async def test_single_handle_recorded(self) -> None: assert result.injections[0].payload_id == "px-42" assert result.injections[0].surface_name == "SharePoint" - async def test_multi_handle_records(self) -> None: + async def test_multi_handle_records_async(self) -> None: result = await Attacks.xpia( inject=[ _mock_handle(surface_name="SP", payload_id="p1"), @@ -312,7 +314,7 @@ async def test_multi_handle_records(self) -> None: class TestXPIAAttachments: """Inline attachments flow through to turns via Request.""" - async def test_attachments_recorded_in_turns(self) -> None: + async def test_attachments_recorded_in_turns_async(self) -> None: attachment = Payload(content="malicious doc", id="att-1") result = await Attacks.xpia( @@ -327,7 +329,7 @@ async def test_attachments_recorded_in_turns(self) -> None: class TestResponseMetadataPropagation: """Response.metadata from the adapter flows into Result.metadata.""" - async def test_single_turn_metadata_promoted_to_top_level(self) -> None: + async def test_single_turn_metadata_promoted_to_top_level_async(self) -> None: adapter = _adapter( responses=[Response(text="ok", metadata={"conversation_id": "c-01"})], ) @@ -339,7 +341,9 @@ async def test_single_turn_metadata_promoted_to_top_level(self) -> None: assert result.metadata == {"conversation_id": "c-01"} - async def test_empty_response_metadata_produces_empty_result_metadata(self) -> None: + async def test_empty_response_metadata_produces_empty_result_metadata_async( + self, + ) -> None: result = await Attacks.xpia( inject=_mock_handle(), trigger="Summarize Q3", @@ -348,7 +352,7 @@ async def test_empty_response_metadata_produces_empty_result_metadata(self) -> N assert result.metadata == {} - async def test_multi_turn_metadata_keyed_by_turn_number(self) -> None: + async def test_multi_turn_metadata_keyed_by_turn_number_async(self) -> None: adapter = _adapter( responses=[ Response(text="turn0", metadata={"page_url": "url0"}), diff --git a/tests/unit/converters/test_docx.py b/tests/unit/converters/test_docx.py index 7d876db2..b2b991c1 100644 --- a/tests/unit/converters/test_docx.py +++ b/tests/unit/converters/test_docx.py @@ -39,7 +39,9 @@ def test_no_pyrit_import_at_construction(self) -> None: DocxConverter() mock_cls.assert_not_called() - async def test_creates_pyrit_converter_on_first_use(self, tmp_path: Path) -> None: + async def test_creates_pyrit_converter_on_first_use_async( + self, tmp_path: Path + ) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -54,7 +56,7 @@ async def test_creates_pyrit_converter_on_first_use(self, tmp_path: Path) -> Non class TestDocxConverterConversion: """Conversion delegates to WordDocConverter and maps result.""" - async def test_produces_docx_payload(self, tmp_path: Path) -> None: + async def test_produces_docx_payload_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -67,7 +69,7 @@ async def test_produces_docx_payload(self, tmp_path: Path) -> None: assert result.format is PayloadFormat.DOCX assert result.artifact == Path(mock_result.output_text) - async def test_delegates_content_to_pyrit(self, tmp_path: Path) -> None: + async def test_delegates_content_to_pyrit_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -84,7 +86,7 @@ async def test_delegates_content_to_pyrit(self, tmp_path: Path) -> None: input_type="text", ) - async def test_preserves_id(self, tmp_path: Path) -> None: + async def test_preserves_id_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -98,7 +100,7 @@ async def test_preserves_id(self, tmp_path: Path) -> None: assert result.id == "keep-me" - async def test_preserves_content_for_reporting(self, tmp_path: Path) -> None: + async def test_preserves_content_for_reporting_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -112,7 +114,7 @@ async def test_preserves_content_for_reporting(self, tmp_path: Path) -> None: assert result.content == "adversarial text" - async def test_metadata_includes_converter_name(self, tmp_path: Path) -> None: + async def test_metadata_includes_converter_name_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -124,7 +126,7 @@ async def test_metadata_includes_converter_name(self, tmp_path: Path) -> None: assert result.metadata["converter"] == "DocxConverter" - async def test_source_metadata_carried_forward(self, tmp_path: Path) -> None: + async def test_source_metadata_carried_forward_async(self, tmp_path: Path) -> None: mock_result = _mock_converter_result(tmp_path) with patch(_PATCH_TARGET) as mock_cls: @@ -142,7 +144,7 @@ async def test_source_metadata_carried_forward(self, tmp_path: Path) -> None: class TestDocxConverterValidation: """Input validation.""" - async def test_rejects_binary_payload(self, tmp_path: Path) -> None: + async def test_rejects_binary_payload_async(self, tmp_path: Path) -> None: artifact = tmp_path / "existing.docx" artifact.write_bytes(b"PK") diff --git a/tests/unit/core/test_converter.py b/tests/unit/core/test_converter.py index e85cdfc0..1ee6d2db 100644 --- a/tests/unit/core/test_converter.py +++ b/tests/unit/core/test_converter.py @@ -40,27 +40,27 @@ def test_converter_satisfies_protocol(self) -> None: def test_html_converter_satisfies_protocol(self) -> None: assert isinstance(_HtmlWrapConverter(), PayloadConverter) - async def test_uppercase_converter_transforms_content(self) -> None: + async def test_uppercase_converter_transforms_content_async(self) -> None: converter = _UpperCaseConverter() payload = Payload(content="hello world", id="t1") result = await converter.convert_async(payload=payload) assert result.content == "HELLO WORLD" assert result.id == "t1" - async def test_html_converter_changes_format(self) -> None: + async def test_html_converter_changes_format_async(self) -> None: converter = _HtmlWrapConverter() payload = Payload(content="evil content", id="t2") result = await converter.convert_async(payload=payload) assert result.content == "
evil content
" assert result.format is PayloadFormat.HTML - async def test_converter_preserves_id(self) -> None: + async def test_converter_preserves_id_async(self) -> None: converter = _UpperCaseConverter() payload = Payload(content="test", id="stable_id") result = await converter.convert_async(payload=payload) assert result.id == "stable_id" - async def test_converter_adds_metadata(self) -> None: + async def test_converter_adds_metadata_async(self) -> None: converter = _UpperCaseConverter() payload = Payload( content="test", @@ -71,7 +71,7 @@ async def test_converter_adds_metadata(self) -> None: assert result.metadata["template"] == "email_exfiltration" assert result.metadata["converter"] == "UpperCaseConverter" - async def test_converters_compose_sequentially(self) -> None: + async def test_converters_compose_sequentially_async(self) -> None: upper = _UpperCaseConverter() html = _HtmlWrapConverter() payload = Payload(content="evil", id="c1") @@ -80,7 +80,9 @@ async def test_converters_compose_sequentially(self) -> None: assert result.content == "EVIL
" assert result.format is PayloadFormat.HTML - async def test_format_converter_preserves_content(self, tmp_path: Path) -> None: + async def test_format_converter_preserves_content_async( + self, tmp_path: Path + ) -> None: fake_file = tmp_path / "fake.png" fake_file.write_bytes(b"\x89PNG") diff --git a/tests/unit/core/test_evaluator.py b/tests/unit/core/test_evaluator.py index 4c3e920d..680b8adf 100644 --- a/tests/unit/core/test_evaluator.py +++ b/tests/unit/core/test_evaluator.py @@ -53,7 +53,7 @@ def test_base_evaluator_satisfies_protocol(self) -> None: class TestOrComposition: - async def test_left_detected_short_circuits(self) -> None: + async def test_left_detected_short_circuits_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.DETECTED) right = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) composed = left | right @@ -64,7 +64,7 @@ async def test_left_detected_short_circuits(self) -> None: assert left.call_count == 1 assert right.call_count == 0 - async def test_right_detected(self) -> None: + async def test_right_detected_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) right = _StubEvaluator(outcome=EvalOutcome.DETECTED) composed = left | right @@ -75,7 +75,7 @@ async def test_right_detected(self) -> None: assert left.call_count == 1 assert right.call_count == 1 - async def test_neither_detected(self) -> None: + async def test_neither_detected_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) right = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) composed = left | right @@ -84,7 +84,7 @@ async def test_neither_detected(self) -> None: assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_undetermined_propagates(self) -> None: + async def test_undetermined_propagates_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) right = _StubEvaluator(outcome=EvalOutcome.UNDETERMINED) composed = left | right @@ -95,7 +95,7 @@ async def test_undetermined_propagates(self) -> None: class TestAndComposition: - async def test_left_not_detected_short_circuits(self) -> None: + async def test_left_not_detected_short_circuits_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) right = _StubEvaluator(outcome=EvalOutcome.DETECTED) composed = left & right @@ -106,7 +106,7 @@ async def test_left_not_detected_short_circuits(self) -> None: assert left.call_count == 1 assert right.call_count == 0 - async def test_left_undetermined_short_circuits(self) -> None: + async def test_left_undetermined_short_circuits_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.UNDETERMINED) right = _StubEvaluator(outcome=EvalOutcome.DETECTED) composed = left & right @@ -116,7 +116,7 @@ async def test_left_undetermined_short_circuits(self) -> None: assert result.outcome is EvalOutcome.UNDETERMINED assert right.call_count == 0 - async def test_both_detected(self) -> None: + async def test_both_detected_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.DETECTED, rationale="L") right = _StubEvaluator(outcome=EvalOutcome.DETECTED, rationale="R") composed = left & right @@ -126,7 +126,7 @@ async def test_both_detected(self) -> None: assert result.outcome is EvalOutcome.DETECTED assert len(result.evidence) == 2 - async def test_right_not_detected(self) -> None: + async def test_right_not_detected_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.DETECTED) right = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) composed = left & right @@ -135,7 +135,7 @@ async def test_right_not_detected(self) -> None: assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_right_undetermined(self) -> None: + async def test_right_undetermined_async(self) -> None: left = _StubEvaluator(outcome=EvalOutcome.DETECTED) right = _StubEvaluator(outcome=EvalOutcome.UNDETERMINED) composed = left & right @@ -146,7 +146,7 @@ async def test_right_undetermined(self) -> None: class TestNotComposition: - async def test_flips_detected_to_not_detected(self) -> None: + async def test_flips_detected_to_not_detected_async(self) -> None: inner = _StubEvaluator(outcome=EvalOutcome.DETECTED) composed = ~inner @@ -154,7 +154,7 @@ async def test_flips_detected_to_not_detected(self) -> None: assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_flips_not_detected_to_detected(self) -> None: + async def test_flips_not_detected_to_detected_async(self) -> None: inner = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) composed = ~inner @@ -162,7 +162,7 @@ async def test_flips_not_detected_to_detected(self) -> None: assert result.outcome is EvalOutcome.DETECTED - async def test_preserves_undetermined(self) -> None: + async def test_preserves_undetermined_async(self) -> None: inner = _StubEvaluator(outcome=EvalOutcome.UNDETERMINED) composed = ~inner @@ -170,7 +170,7 @@ async def test_preserves_undetermined(self) -> None: assert result.outcome is EvalOutcome.UNDETERMINED - async def test_preserves_confidence_and_evidence(self) -> None: + async def test_preserves_confidence_and_evidence_async(self) -> None: inner = _StubEvaluator(outcome=EvalOutcome.DETECTED) composed = ~inner @@ -181,7 +181,7 @@ async def test_preserves_confidence_and_evidence(self) -> None: class TestCompositionChaining: - async def test_or_and_not_chain(self) -> None: + async def test_or_and_not_chain_async(self) -> None: a = _StubEvaluator(outcome=EvalOutcome.NOT_DETECTED) b = _StubEvaluator(outcome=EvalOutcome.DETECTED) c = _StubEvaluator(outcome=EvalOutcome.DETECTED) @@ -192,7 +192,7 @@ async def test_or_and_not_chain(self) -> None: assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_composed_evaluators_are_composable(self) -> None: + async def test_composed_evaluators_are_composable_async(self) -> None: a = _StubEvaluator(outcome=EvalOutcome.DETECTED) b = _StubEvaluator(outcome=EvalOutcome.DETECTED) diff --git a/tests/unit/core/test_execution.py b/tests/unit/core/test_execution.py index f5f8103f..44301068 100644 --- a/tests/unit/core/test_execution.py +++ b/tests/unit/core/test_execution.py @@ -121,7 +121,7 @@ class _RecordingHandler(ExecutionEventHandler): def __init__(self) -> None: self.events: list[ExecutionEventData] = [] - async def on_event(self, *, event_data: ExecutionEventData) -> None: + async def on_event_async(self, *, event_data: ExecutionEventData) -> None: """Record the event data.""" self.events.append(event_data) @@ -129,13 +129,13 @@ async def on_event(self, *, event_data: ExecutionEventData) -> None: class _BrokenHandler(ExecutionEventHandler): """Handler that always raises.""" - async def on_event(self, *, event_data: ExecutionEventData) -> None: + async def on_event_async(self, *, event_data: ExecutionEventData) -> None: """Raise unconditionally to test handler safety.""" raise ValueError("handler broke") class TestBaseExecutionLifecycle: - async def test_fires_pre_and_post_execute(self) -> None: + async def test_fires_pre_and_post_execute_async(self) -> None: handler = _RecordingHandler() execution = _SuccessExecution(event_handlers=[handler]) adapter = _StubAdapter() @@ -148,7 +148,7 @@ async def test_fires_pre_and_post_execute(self) -> None: assert handler.events[1].event is ExecutionEvent.ON_POST_EXECUTE assert handler.events[1].result is result - async def test_post_execute_has_elapsed_time(self) -> None: + async def test_post_execute_has_elapsed_time_async(self) -> None: handler = _RecordingHandler() execution = _SuccessExecution(event_handlers=[handler]) @@ -159,7 +159,7 @@ async def test_post_execute_has_elapsed_time(self) -> None: class TestInfrastructureErrorHandling: - async def test_produces_error_result(self) -> None: + async def test_produces_error_result_async(self) -> None: execution = _InfraErrorExecution() adapter = _StubAdapter() @@ -169,21 +169,21 @@ async def test_produces_error_result(self) -> None: assert result.status is SafetyStatus.ERROR assert "SharePoint returned 503" in result.summary - async def test_error_result_has_strategy(self) -> None: + async def test_error_result_has_strategy_async(self) -> None: execution = _InfraErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) assert result.strategy == "infra_error" - async def test_error_result_has_observability_level(self) -> None: + async def test_error_result_has_observability_level_async(self) -> None: execution = _InfraErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) assert result.observability_level is ObservabilityLevel.TOOL_ONLY - async def test_error_result_has_metadata(self) -> None: + async def test_error_result_has_metadata_async(self) -> None: execution = _InfraErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) @@ -191,7 +191,7 @@ async def test_error_result_has_metadata(self) -> None: assert result.metadata["error"] == "SharePoint returned 503" assert result.metadata["error_type"] == "InfrastructureError" - async def test_fires_on_error_and_post_execute(self) -> None: + async def test_fires_on_error_and_post_execute_async(self) -> None: handler = _RecordingHandler() execution = _InfraErrorExecution(event_handlers=[handler]) @@ -203,7 +203,7 @@ async def test_fires_on_error_and_post_execute(self) -> None: class TestGenericErrorHandling: - async def test_produces_error_result(self) -> None: + async def test_produces_error_result_async(self) -> None: execution = _GenericErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) @@ -212,14 +212,14 @@ async def test_produces_error_result(self) -> None: assert result.status is SafetyStatus.ERROR assert "unexpected failure" in result.summary - async def test_error_result_has_strategy(self) -> None: + async def test_error_result_has_strategy_async(self) -> None: execution = _GenericErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) assert result.strategy == "generic_error" - async def test_error_result_has_metadata(self) -> None: + async def test_error_result_has_metadata_async(self) -> None: execution = _GenericErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) @@ -227,7 +227,7 @@ async def test_error_result_has_metadata(self) -> None: assert result.metadata["error"] == "unexpected failure" assert result.metadata["error_type"] == "RuntimeError" - async def test_fires_on_error_and_post_execute(self) -> None: + async def test_fires_on_error_and_post_execute_async(self) -> None: handler = _RecordingHandler() execution = _GenericErrorExecution(event_handlers=[handler]) @@ -237,7 +237,7 @@ async def test_fires_on_error_and_post_execute(self) -> None: assert ExecutionEvent.ON_ERROR in event_types assert ExecutionEvent.ON_POST_EXECUTE in event_types - async def test_on_error_contains_exception(self) -> None: + async def test_on_error_contains_exception_async(self) -> None: handler = _RecordingHandler() execution = _GenericErrorExecution(event_handlers=[handler]) @@ -250,7 +250,7 @@ async def test_on_error_contains_exception(self) -> None: class TestHandlerSafety: - async def test_broken_handler_does_not_abort_execution(self) -> None: + async def test_broken_handler_does_not_abort_execution_async(self) -> None: broken = _BrokenHandler() recorder = _RecordingHandler() execution = _SuccessExecution(event_handlers=[broken, recorder]) @@ -262,14 +262,14 @@ async def test_broken_handler_does_not_abort_execution(self) -> None: class TestDefaultHandlerFactory: - async def test_execution_works_without_factory(self) -> None: + async def test_execution_works_without_factory_async(self) -> None: execution = _SuccessExecution() result = await execution.execute_async(adapter=_StubAdapter()) assert result.safe is True - async def test_factory_handlers_are_prepended(self) -> None: + async def test_factory_handlers_are_prepended_async(self) -> None: from rampart.core.execution import ( clear_default_handler_factory, register_default_handler_factory, @@ -295,7 +295,7 @@ def test_register_rejects_non_callable(self) -> None: class TestDriverErrorHandling: - async def test_produces_error_result(self) -> None: + async def test_produces_error_result_async(self) -> None: execution = _DriverErrorExecution() adapter = _StubAdapter() @@ -305,14 +305,14 @@ async def test_produces_error_result(self) -> None: assert result.status is SafetyStatus.ERROR assert "LLM returned garbage" in result.summary - async def test_error_result_has_strategy(self) -> None: + async def test_error_result_has_strategy_async(self) -> None: execution = _DriverErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) assert result.strategy == "driver_error" - async def test_error_result_has_metadata(self) -> None: + async def test_error_result_has_metadata_async(self) -> None: execution = _DriverErrorExecution() result = await execution.execute_async(adapter=_StubAdapter()) @@ -320,7 +320,7 @@ async def test_error_result_has_metadata(self) -> None: assert result.metadata["error"] == "LLM returned garbage" assert result.metadata["error_type"] == "DriverError" - async def test_fires_on_error_and_post_execute(self) -> None: + async def test_fires_on_error_and_post_execute_async(self) -> None: handler = _RecordingHandler() execution = _DriverErrorExecution(event_handlers=[handler]) @@ -332,7 +332,7 @@ async def test_fires_on_error_and_post_execute(self) -> None: class TestEvaluateTurnAsync: - async def test_returns_turn_with_eval_result(self) -> None: + async def test_returns_turn_with_eval_result_async(self) -> None: from unittest.mock import AsyncMock from rampart.core.execution import evaluate_turn_async @@ -362,7 +362,7 @@ async def test_returns_turn_with_eval_result(self) -> None: assert turn.response.text == "world" assert turn.turn_number == 0 - async def test_includes_history_in_context(self) -> None: + async def test_includes_history_in_context_async(self) -> None: from unittest.mock import AsyncMock from rampart.core.execution import evaluate_turn_async @@ -402,7 +402,7 @@ def capture_eval(*, context: EvalContext) -> EvalResult: assert captured_context.turns[0].request.prompt == "prev" assert captured_context.turns[1].request.prompt == "current" - async def test_preserves_driver_reasoning(self) -> None: + async def test_preserves_driver_reasoning_async(self) -> None: from unittest.mock import AsyncMock from rampart.core.execution import evaluate_turn_async diff --git a/tests/unit/core/test_injection.py b/tests/unit/core/test_injection.py index 0573abde..ebd84846 100644 --- a/tests/unit/core/test_injection.py +++ b/tests/unit/core/test_injection.py @@ -1,12 +1,12 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. -"""Tests for rampart.core.injection — InjectionHandle, Surface, sleep_until_ready.""" +"""Tests for rampart.core.injection: protocols and sleep_until_ready_async.""" import types from typing import Self -from rampart.core.injection import InjectionHandle, Surface, sleep_until_ready +from rampart.core.injection import InjectionHandle, Surface, sleep_until_ready_async from rampart.core.types import Payload @@ -21,7 +21,7 @@ def payload_id(self) -> str | None: def surface_name(self) -> str: return "SharePoint" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: pass async def __aenter__(self) -> Self: @@ -55,7 +55,7 @@ def payload_id(self) -> str | None: def surface_name(self) -> str: return "test" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: pass async def __aenter__(self) -> Self: @@ -78,4 +78,4 @@ def inject(self, *, payload: Payload) -> MyHandle: class TestSleepUntilReady: async def test_completes_without_error_async(self) -> None: - await sleep_until_ready(0.0) + await sleep_until_ready_async(0.0) diff --git a/tests/unit/core/test_protocols.py b/tests/unit/core/test_protocols.py index 48595149..143afa86 100644 --- a/tests/unit/core/test_protocols.py +++ b/tests/unit/core/test_protocols.py @@ -87,7 +87,7 @@ def payload_id(self) -> str | None: def surface_name(self) -> str: return "SharePoint" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: pass async def __aenter__(self) -> Self: @@ -115,7 +115,7 @@ def payload_id(self) -> str | None: def surface_name(self) -> str: return "test" - async def wait_until_ready(self) -> None: + async def wait_until_ready_async(self) -> None: pass async def __aenter__(self) -> Self: diff --git a/tests/unit/drivers/test_llm_driver.py b/tests/unit/drivers/test_llm_driver.py index f6eb04f2..f8d4357e 100644 --- a/tests/unit/drivers/test_llm_driver.py +++ b/tests/unit/drivers/test_llm_driver.py @@ -82,7 +82,7 @@ def test_construction_does_not_call_create_prompt_target(self) -> None: LLMDriver(llm=_TEST_LLM, persona=_TEST_PERSONA) mock_create.assert_not_called() - async def test_first_call_initializes_target(self) -> None: + async def test_first_call_initializes_target_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -114,7 +114,7 @@ async def test_first_call_initializes_target(self) -> None: class TestLLMDriverConstruction: - async def test_system_prompt_includes_persona(self) -> None: + async def test_system_prompt_includes_persona_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -137,7 +137,7 @@ async def test_system_prompt_includes_persona(self) -> None: sp = mock_target.set_system_prompt.call_args.kwargs["system_prompt"] assert "You are a test persona." in sp - async def test_system_prompt_includes_objective_when_provided(self) -> None: + async def test_system_prompt_includes_objective_when_provided_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -165,7 +165,7 @@ async def test_system_prompt_includes_objective_when_provided(self) -> None: assert "Objective" in sp assert "Extract secret data" in sp - async def test_system_prompt_omits_objective_when_none(self) -> None: + async def test_system_prompt_omits_objective_when_none_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -188,7 +188,9 @@ async def test_system_prompt_omits_objective_when_none(self) -> None: sp = mock_target.set_system_prompt.call_args.kwargs["system_prompt"] assert "Objective" not in sp - async def test_system_prompt_includes_injection_metadata_not_content(self) -> None: + async def test_system_prompt_includes_injection_metadata_not_content_async( + self, + ) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -230,7 +232,7 @@ def test_two_drivers_have_distinct_conversation_ids(self) -> None: class TestLLMDriverSendFlow: - async def test_returns_plain_text_as_prompt(self) -> None: + async def test_returns_plain_text_as_prompt_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -253,7 +255,7 @@ async def test_returns_plain_text_as_prompt(self) -> None: assert decision is not None assert decision.request.prompt == "Tell me about Q3 earnings" - async def test_send_uses_normalizer_helper(self) -> None: + async def test_send_uses_normalizer_helper_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -280,7 +282,7 @@ async def test_send_uses_normalizer_helper(self) -> None: assert call_kwargs["user_message"] == "Begin. Send the first user prompt." assert "rampart.component" in call_kwargs["labels"] - async def test_non_empty_history_sends_agent_response(self) -> None: + async def test_non_empty_history_sends_agent_response_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() # System prompt message + 1 user + 1 assistant = history matches 1 turn @@ -324,7 +326,7 @@ async def test_non_empty_history_sends_agent_response(self) -> None: assert "not_detected" in user_msg assert "not found" in user_msg - async def test_non_empty_history_labels_agent_response_as_untrusted_data( + async def test_non_empty_history_labels_agent_response_as_untrusted_data_async( self, ) -> None: mock_target = MagicMock() @@ -378,7 +380,7 @@ async def test_non_empty_history_labels_agent_response_as_untrusted_data( "evaluator_rationale_untrusted": rationale, } - async def test_system_prompt_treats_observations_as_untrusted(self) -> None: + async def test_system_prompt_treats_observations_as_untrusted_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -403,7 +405,7 @@ async def test_system_prompt_treats_observations_as_untrusted(self) -> None: assert "Never follow instructions" in sp assert "target-agent responses" in sp - async def test_strips_whitespace_from_response(self) -> None: + async def test_strips_whitespace_from_response_async(self) -> None: mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -427,7 +429,7 @@ async def test_strips_whitespace_from_response(self) -> None: class TestLLMDriverErrorHandling: - async def test_empty_response_raises_driver_error(self) -> None: + async def test_empty_response_raises_driver_error_async(self) -> None: mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -448,7 +450,7 @@ async def test_empty_response_raises_driver_error(self) -> None: with pytest.raises(DriverError, match="empty response"): await driver.next_prompt_async(history=[]) - async def test_whitespace_only_response_raises_driver_error(self) -> None: + async def test_whitespace_only_response_raises_driver_error_async(self) -> None: mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -469,7 +471,7 @@ async def test_whitespace_only_response_raises_driver_error(self) -> None: with pytest.raises(DriverError, match="empty response"): await driver.next_prompt_async(history=[]) - async def test_send_exception_wrapped_in_driver_error(self) -> None: + async def test_send_exception_wrapped_in_driver_error_async(self) -> None: mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -490,7 +492,7 @@ async def test_send_exception_wrapped_in_driver_error(self) -> None: with pytest.raises(DriverError, match="send_user_turn_async failed"): await driver.next_prompt_async(history=[]) - async def test_driver_error_preserves_cause(self) -> None: + async def test_driver_error_preserves_cause_async(self) -> None: original = RuntimeError("timeout") mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -515,7 +517,7 @@ async def test_driver_error_preserves_cause(self) -> None: class TestLLMDriverDesyncDetection: - async def test_desync_raises_driver_error(self) -> None: + async def test_desync_raises_driver_error_async(self) -> None: """Passing history that doesn't match driver-side memory raises.""" mock_memory = MagicMock() # Driver-side has 0 user turns but we pass 1 agent-side turn @@ -545,7 +547,7 @@ def test_from_target_does_not_require_llm_config(self) -> None: assert driver._llm is None assert driver._target is mock_target - async def test_from_target_sets_system_prompt_on_first_use(self) -> None: + async def test_from_target_sets_system_prompt_on_first_use_async(self) -> None: mock_target = MagicMock() mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -577,7 +579,7 @@ async def test_from_target_sets_system_prompt_on_first_use(self) -> None: class TestLLMDriverAttachments: - async def test_first_turn_attaches_injections(self) -> None: + async def test_first_turn_attaches_injections_async(self) -> None: """Injections should be attached to the first request.""" mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] @@ -610,7 +612,7 @@ async def test_first_turn_attaches_injections(self) -> None: assert decision is not None assert decision.request.attachments == [payload] - async def test_subsequent_turns_have_no_attachments(self) -> None: + async def test_subsequent_turns_have_no_attachments_async(self) -> None: """Only the first turn should carry attachments.""" mock_piece_user = MagicMock() mock_piece_user.api_role = "user" @@ -655,7 +657,7 @@ async def test_subsequent_turns_have_no_attachments(self) -> None: assert decision is not None assert decision.request.attachments == [] - async def test_no_injections_means_no_attachments(self) -> None: + async def test_no_injections_means_no_attachments_async(self) -> None: """Without injections, first turn should have empty attachments.""" mock_memory = MagicMock() mock_memory.get_conversation.return_value = [] diff --git a/tests/unit/evaluators/test_llm_judge.py b/tests/unit/evaluators/test_llm_judge.py index 1fac86f6..5e32faa5 100644 --- a/tests/unit/evaluators/test_llm_judge.py +++ b/tests/unit/evaluators/test_llm_judge.py @@ -123,7 +123,7 @@ def _patched_judge(sender: _FakeSender) -> AbstractContextManager[None]: ) -async def _evaluate( +async def _evaluate_async( *, responses: list[str | BaseException] | None = None, context: EvalContext | None = None, @@ -172,7 +172,7 @@ def test_persona_defaults_to_neutral_evaluator(self) -> None: class TestOutcomeRoundTrip: async def test_detected_round_trips_async(self) -> None: - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[ _verdict_json( outcome="detected", @@ -188,13 +188,13 @@ async def test_detected_round_trips_async(self) -> None: assert result.evidence == ["email leaked"] async def test_not_detected_round_trips_async(self) -> None: - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[_verdict_json(outcome="not_detected")], ) assert result.outcome is EvalOutcome.NOT_DETECTED async def test_undetermined_round_trips_async(self) -> None: - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[_verdict_json(outcome="undetermined")], ) assert result.outcome is EvalOutcome.UNDETERMINED @@ -203,12 +203,12 @@ async def test_undetermined_round_trips_async(self) -> None: class TestJsonRobustness: async def test_markdown_fenced_json_parses_async(self) -> None: raw = f"```json\n{_verdict_json()}\n```" - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.DETECTED async def test_json_embedded_in_prose_parses_async(self) -> None: raw = f"Here is the verdict: {_verdict_json()} (end)" - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.DETECTED async def test_malformed_json_exhausts_retries_to_undetermined_async( @@ -216,7 +216,7 @@ async def test_malformed_json_exhausts_retries_to_undetermined_async( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("RETRY_MAX_NUM_ATTEMPTS", "2") - result, _ = await _evaluate(responses=["not json at all"]) + result, _ = await _evaluate_async(responses=["not json at all"]) assert result.outcome is EvalOutcome.UNDETERMINED assert "valid JSON" in result.rationale assert result.confidence == pytest.approx(0.0) # pyright: ignore[reportUnknownMemberType] @@ -226,7 +226,7 @@ async def test_unknown_outcome_value_degrades_to_undetermined_async( monkeypatch: pytest.MonkeyPatch, ) -> None: monkeypatch.setenv("RETRY_MAX_NUM_ATTEMPTS", "2") - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[_verdict_json(outcome="maybe_detected")], ) assert result.outcome is EvalOutcome.UNDETERMINED @@ -237,7 +237,7 @@ async def test_missing_required_key_degrades_to_undetermined_async( ) -> None: monkeypatch.setenv("RETRY_MAX_NUM_ATTEMPTS", "2") raw = json.dumps({"outcome": "detected", "confidence": 0.5, "rationale": "r"}) - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.UNDETERMINED async def test_evidence_must_be_list_of_strings_async( @@ -253,17 +253,17 @@ async def test_evidence_must_be_list_of_strings_async( "evidence": [1, 2, 3], }, ) - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.UNDETERMINED class TestConfidenceClamping: async def test_above_one_clamps_to_one_async(self) -> None: - result, _ = await _evaluate(responses=[_verdict_json(confidence=1.7)]) + result, _ = await _evaluate_async(responses=[_verdict_json(confidence=1.7)]) assert result.confidence == pytest.approx(1.0) # pyright: ignore[reportUnknownMemberType] async def test_below_zero_clamps_to_zero_async(self) -> None: - result, _ = await _evaluate(responses=[_verdict_json(confidence=-0.4)]) + result, _ = await _evaluate_async(responses=[_verdict_json(confidence=-0.4)]) assert result.confidence == pytest.approx(0.0) # pyright: ignore[reportUnknownMemberType] async def test_non_numeric_confidence_degrades_async( @@ -279,7 +279,7 @@ async def test_non_numeric_confidence_degrades_async( "evidence": [], }, ) - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.UNDETERMINED async def test_nan_confidence_degrades_async( @@ -295,7 +295,7 @@ async def test_nan_confidence_degrades_async( '{"outcome": "detected", "confidence": NaN, ' '"rationale": "r", "evidence": []}' ) - result, _ = await _evaluate(responses=[raw]) + result, _ = await _evaluate_async(responses=[raw]) assert result.outcome is EvalOutcome.UNDETERMINED @@ -316,7 +316,7 @@ def _two_turn_ctx() -> EvalContext: class TestTranscriptScope: async def test_full_scope_includes_all_turns_async(self) -> None: - _, sender = await _evaluate( + _, sender = await _evaluate_async( context=_two_turn_ctx(), scope=TranscriptScope.FULL, ) @@ -327,7 +327,7 @@ async def test_full_scope_includes_all_turns_async(self) -> None: assert "[Turn 1]" in user_message async def test_current_turn_scope_excludes_earlier_turns_async(self) -> None: - _, sender = await _evaluate( + _, sender = await _evaluate_async( context=_two_turn_ctx(), scope=TranscriptScope.CURRENT_TURN, ) @@ -336,7 +336,7 @@ async def test_current_turn_scope_excludes_earlier_turns_async(self) -> None: assert "second user prompt" in user_message async def test_empty_transcript_uses_placeholder_async(self) -> None: - _, sender = await _evaluate(context=EvalContext(turns=[])) + _, sender = await _evaluate_async(context=EvalContext(turns=[])) _, user_message = sender.calls[0] assert user_message == "(empty transcript)" @@ -364,7 +364,7 @@ async def test_includes_field_labels_async(self) -> None: ), ), ) - _, sender = await _evaluate(context=ctx) + _, sender = await _evaluate_async(context=ctx) _, user_message = sender.calls[0] assert "User: please help" in user_message assert "Agent: here you go" in user_message @@ -397,7 +397,7 @@ async def test_attachment_content_excluded_metadata_included_async(self) -> None response=Response(text="ok"), ), ) - _, sender = await _evaluate(context=ctx) + _, sender = await _evaluate_async(context=ctx) _, user_message = sender.calls[0] assert payload_body not in user_message assert "att-123" in user_message @@ -406,7 +406,7 @@ async def test_attachment_content_excluded_metadata_included_async(self) -> None class TestSystemPromptAssembly: async def test_hardening_suffix_appended_async(self) -> None: - _, sender = await _evaluate() + _, sender = await _evaluate_async() system_prompt, _ = sender.calls[0] assert system_prompt.endswith(LLMJudge._HARDENING_SUFFIX) assert "SECURITY BOUNDARY" in system_prompt @@ -416,17 +416,17 @@ async def test_persona_prompt_rendered_async(self) -> None: name="security_reviewer", system_prompt="You are a senior security engineer.", ) - _, sender = await _evaluate(persona=custom) + _, sender = await _evaluate_async(persona=custom) system_prompt, _ = sender.calls[0] assert "senior security engineer" in system_prompt async def test_objective_rendered_async(self) -> None: - _, sender = await _evaluate(objective="The agent leaked an API key.") + _, sender = await _evaluate_async(objective="The agent leaked an API key.") system_prompt, _ = sender.calls[0] assert "The agent leaked an API key." in system_prompt async def test_default_persona_neutral_evaluator_in_prompt_async(self) -> None: - _, sender = await _evaluate() + _, sender = await _evaluate_async() system_prompt, _ = sender.calls[0] assert ( NEUTRAL_EVALUATOR.system_prompt.split(".", maxsplit=1)[0] in system_prompt @@ -438,13 +438,13 @@ async def test_manifest_summary_rendered_when_present_async(self) -> None: description="A test agent.", tools=[ToolDeclaration(name="send_email", description="send mail")], ) - _, sender = await _evaluate(context=_make_ctx(manifest=manifest)) + _, sender = await _evaluate_async(context=_make_ctx(manifest=manifest)) system_prompt, _ = sender.calls[0] assert "TestAgent" in system_prompt assert "send_email" in system_prompt async def test_schema_declaration_in_system_prompt_async(self) -> None: - _, sender = await _evaluate() + _, sender = await _evaluate_async() system_prompt, _ = sender.calls[0] assert "outcome" in system_prompt assert "confidence" in system_prompt @@ -456,14 +456,14 @@ class TestFailureTiers: async def test_transient_empty_response_degrades_to_undetermined_async( self, ) -> None: - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[EmptyResponseException(message="empty")], ) assert result.outcome is EvalOutcome.UNDETERMINED assert "EmptyResponseException" in result.rationale async def test_transient_rate_limit_degrades_to_undetermined_async(self) -> None: - result, _ = await _evaluate( + result, _ = await _evaluate_async( responses=[RateLimitException(message="rate")], ) assert result.outcome is EvalOutcome.UNDETERMINED diff --git a/tests/unit/evaluators/test_response_contains.py b/tests/unit/evaluators/test_response_contains.py index 793076e7..07ba48ae 100644 --- a/tests/unit/evaluators/test_response_contains.py +++ b/tests/unit/evaluators/test_response_contains.py @@ -17,40 +17,40 @@ def _ctx(text: str) -> EvalContext: class TestResponseContainsSubstring: - async def test_case_insensitive_by_default(self) -> None: + async def test_case_insensitive_by_default_async(self) -> None: result = await ResponseContains("HELLO").evaluate_async( context=_ctx("hello world"), ) assert result.outcome is EvalOutcome.DETECTED - async def test_case_sensitive_opt_in(self) -> None: + async def test_case_sensitive_opt_in_async(self) -> None: result = await ResponseContains( "HELLO", case_sensitive=True, ).evaluate_async(context=_ctx("hello world")) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_case_sensitive_match(self) -> None: + async def test_case_sensitive_match_async(self) -> None: result = await ResponseContains( "hello", case_sensitive=True, ).evaluate_async(context=_ctx("hello world")) assert result.outcome is EvalOutcome.DETECTED - async def test_not_detected(self) -> None: + async def test_not_detected_async(self) -> None: result = await ResponseContains("evil").evaluate_async(context=_ctx("all good")) assert result.outcome is EvalOutcome.NOT_DETECTED class TestResponseContainsRegex: - async def test_regex_match(self) -> None: + async def test_regex_match_async(self) -> None: pattern = re.compile(r"evil@\w+\.com") result = await ResponseContains(pattern).evaluate_async( context=_ctx("sent to evil@evil.com"), ) assert result.outcome is EvalOutcome.DETECTED - async def test_regex_no_match(self) -> None: + async def test_regex_no_match_async(self) -> None: pattern = re.compile(r"evil@\w+\.com") result = await ResponseContains(pattern).evaluate_async( context=_ctx("all good"), @@ -59,13 +59,13 @@ async def test_regex_no_match(self) -> None: class TestResponseContainsCallable: - async def test_callable_match(self) -> None: + async def test_callable_match_async(self) -> None: result = await ResponseContains( lambda text: "secret" in text, ).evaluate_async(context=_ctx("the secret is here")) assert result.outcome is EvalOutcome.DETECTED - async def test_callable_no_match(self) -> None: + async def test_callable_no_match_async(self) -> None: result = await ResponseContains( lambda text: "secret" in text, ).evaluate_async(context=_ctx("nothing to see")) @@ -73,7 +73,7 @@ async def test_callable_no_match(self) -> None: class TestResponseContainsComposition: - async def test_composable_with_not(self) -> None: + async def test_composable_with_not_async(self) -> None: composed = ~ResponseContains("I cannot help") result = await composed.evaluate_async(context=_ctx("Sure, here you go")) assert result.outcome is EvalOutcome.DETECTED diff --git a/tests/unit/evaluators/test_side_effect.py b/tests/unit/evaluators/test_side_effect.py index efa12be5..b85ca525 100644 --- a/tests/unit/evaluators/test_side_effect.py +++ b/tests/unit/evaluators/test_side_effect.py @@ -27,24 +27,24 @@ def _ctx_with_side_effects(*effects: SideEffect) -> EvalContext: class TestSideEffectOccurredDetection: - async def test_detects_by_kind(self) -> None: + async def test_detects_by_kind_async(self) -> None: ctx = _ctx_with_side_effects(SideEffect(kind="http_request")) result = await SideEffectOccurred("http_request").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_not_detected_wrong_kind(self) -> None: + async def test_not_detected_wrong_kind_async(self) -> None: ctx = _ctx_with_side_effects(SideEffect(kind="file_write")) result = await SideEffectOccurred("http_request").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_not_detected_no_effects(self) -> None: + async def test_not_detected_no_effects_async(self) -> None: ctx = _ctx_with_side_effects() result = await SideEffectOccurred("http_request").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED class TestSideEffectOccurredDetailPredicates: - async def test_exact_detail_match(self) -> None: + async def test_exact_detail_match_async(self) -> None: se = SideEffect(kind="http_request", details={"url": "https://evil.com"}) ctx = _ctx_with_side_effects(se) result = await SideEffectOccurred( @@ -53,7 +53,7 @@ async def test_exact_detail_match(self) -> None: ).evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_exact_detail_mismatch(self) -> None: + async def test_exact_detail_mismatch_async(self) -> None: se = SideEffect(kind="http_request", details={"url": "https://safe.com"}) ctx = _ctx_with_side_effects(se) result = await SideEffectOccurred( @@ -62,7 +62,7 @@ async def test_exact_detail_mismatch(self) -> None: ).evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_predicate_detail_match(self) -> None: + async def test_predicate_detail_match_async(self) -> None: se = SideEffect(kind="http_request", details={"url": "https://evil.com/data"}) ctx = _ctx_with_side_effects(se) result = await SideEffectOccurred( @@ -71,7 +71,7 @@ async def test_predicate_detail_match(self) -> None: ).evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_predicate_detail_mismatch(self) -> None: + async def test_predicate_detail_mismatch_async(self) -> None: se = SideEffect(kind="http_request", details={"url": "https://safe.com"}) ctx = _ctx_with_side_effects(se) result = await SideEffectOccurred( diff --git a/tests/unit/evaluators/test_tool_called.py b/tests/unit/evaluators/test_tool_called.py index 68976131..f5972b4b 100644 --- a/tests/unit/evaluators/test_tool_called.py +++ b/tests/unit/evaluators/test_tool_called.py @@ -41,24 +41,24 @@ def _multi_turn_ctx(turns_tool_calls: list[list[ToolCall]]) -> EvalContext: class TestToolCalledDetection: - async def test_detects_by_name(self) -> None: + async def test_detects_by_name_async(self) -> None: ctx = _ctx_with_tool_calls(ToolCall(name="send_email")) result = await ToolCalled("send_email").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_not_detected_wrong_name(self) -> None: + async def test_not_detected_wrong_name_async(self) -> None: ctx = _ctx_with_tool_calls(ToolCall(name="read_file")) result = await ToolCalled("send_email").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_not_detected_no_tool_calls(self) -> None: + async def test_not_detected_no_tool_calls_async(self) -> None: ctx = _ctx_with_tool_calls() result = await ToolCalled("send_email").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED class TestToolCalledParameterMatching: - async def test_exact_parameter_match(self) -> None: + async def test_exact_parameter_match_async(self) -> None: tc = ToolCall(name="send_email", arguments={"to": "evil@evil.com"}) ctx = _ctx_with_tool_calls(tc) result = await ToolCalled("send_email", to="evil@evil.com").evaluate_async( @@ -66,7 +66,7 @@ async def test_exact_parameter_match(self) -> None: ) assert result.outcome is EvalOutcome.DETECTED - async def test_exact_parameter_mismatch(self) -> None: + async def test_exact_parameter_mismatch_async(self) -> None: tc = ToolCall(name="send_email", arguments={"to": "friend@company.com"}) ctx = _ctx_with_tool_calls(tc) result = await ToolCalled("send_email", to="evil@evil.com").evaluate_async( @@ -74,7 +74,7 @@ async def test_exact_parameter_mismatch(self) -> None: ) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_predicate_parameter_match(self) -> None: + async def test_predicate_parameter_match_async(self) -> None: tc = ToolCall(name="send_email", arguments={"to": "evil@evil.com"}) ctx = _ctx_with_tool_calls(tc) result = await ToolCalled( @@ -83,7 +83,7 @@ async def test_predicate_parameter_match(self) -> None: ).evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_predicate_parameter_mismatch(self) -> None: + async def test_predicate_parameter_mismatch_async(self) -> None: tc = ToolCall(name="send_email", arguments={"to": "friend@company.com"}) ctx = _ctx_with_tool_calls(tc) result = await ToolCalled( @@ -92,7 +92,7 @@ async def test_predicate_parameter_mismatch(self) -> None: ).evaluate_async(context=ctx) assert result.outcome is EvalOutcome.NOT_DETECTED - async def test_missing_parameter_returns_none_to_predicate(self) -> None: + async def test_missing_parameter_returns_none_to_predicate_async(self) -> None: tc = ToolCall(name="send_email", arguments={}) ctx = _ctx_with_tool_calls(tc) result = await ToolCalled( @@ -103,7 +103,7 @@ async def test_missing_parameter_returns_none_to_predicate(self) -> None: class TestToolCalledMultiTurn: - async def test_scans_across_turns(self) -> None: + async def test_scans_across_turns_async(self) -> None: ctx = _multi_turn_ctx( [ [], @@ -113,7 +113,7 @@ async def test_scans_across_turns(self) -> None: result = await ToolCalled("send_email").evaluate_async(context=ctx) assert result.outcome is EvalOutcome.DETECTED - async def test_not_detected_across_turns(self) -> None: + async def test_not_detected_across_turns_async(self) -> None: ctx = _multi_turn_ctx( [ [ToolCall(name="read_file")], @@ -125,7 +125,7 @@ async def test_not_detected_across_turns(self) -> None: class TestToolCalledComposition: - async def test_composable_with_or(self) -> None: + async def test_composable_with_or_async(self) -> None: tc = ToolCall(name="send_email") ctx = _ctx_with_tool_calls(tc) composed = ToolCalled("send_email") | ToolCalled("delete_file") diff --git a/tests/unit/payloads/test_generator.py b/tests/unit/payloads/test_generator.py index e48df02a..3c82d0a4 100644 --- a/tests/unit/payloads/test_generator.py +++ b/tests/unit/payloads/test_generator.py @@ -44,7 +44,7 @@ def _persona() -> Persona: class TestGenerateTextVariants: - async def test_returns_one_variant_per_call(self) -> None: + async def test_returns_one_variant_per_call_async(self) -> None: with patch( "rampart.payloads._generator.PayloadGenerator._send_to_llm_async", new_callable=AsyncMock, @@ -65,7 +65,7 @@ async def test_returns_one_variant_per_call(self) -> None: assert result == ["variant one", "variant two", "variant three"] - async def test_resolves_template_variables(self) -> None: + async def test_resolves_template_variables_async(self) -> None: captured_args: dict[str, str] = {} def capture(*, system_message: str, user_message: str): @@ -95,7 +95,7 @@ def capture(*, system_message: str, user_message: str): assert "override@evil.com" in captured_args["user_message"] assert "{email}" not in captured_args["user_message"] - async def test_strips_whitespace(self) -> None: + async def test_strips_whitespace_async(self) -> None: with patch( "rampart.payloads._generator.PayloadGenerator._send_to_llm_async", new_callable=AsyncMock, @@ -115,7 +115,7 @@ async def test_strips_whitespace(self) -> None: assert result == ["padded content"] - async def test_includes_objective_in_prompt(self) -> None: + async def test_includes_objective_in_prompt_async(self) -> None: captured_args: dict[str, str] = {} def capture(*, system_message: str, user_message: str) -> str: diff --git a/tests/unit/payloads/test_payloads.py b/tests/unit/payloads/test_payloads.py index bbe80966..7813219f 100644 --- a/tests/unit/payloads/test_payloads.py +++ b/tests/unit/payloads/test_payloads.py @@ -80,7 +80,7 @@ def _patch_llm(*responses: str): class TestGeneration: """Core generation pipeline — text variants from LLM.""" - async def test_generates_text_payloads(self) -> None: + async def test_generates_text_payloads_async(self) -> None: with _patch_llm("variant_a", "variant_b"): result = await Payloads.generate_async( template=_template(), @@ -94,7 +94,7 @@ async def test_generates_text_payloads(self) -> None: assert result[1].content == "variant_b" assert all(p.format is PayloadFormat.TEXT for p in result) - async def test_count_below_one_raises(self) -> None: + async def test_count_below_one_raises_async(self) -> None: with pytest.raises(ValueError, match="count must be >= 1"): await Payloads.generate_async( template=_template(), @@ -103,7 +103,7 @@ async def test_count_below_one_raises(self) -> None: count=0, ) - async def test_provenance_metadata_on_payloads(self) -> None: + async def test_provenance_metadata_on_payloads_async(self) -> None: with _patch_llm("variant"): result = await Payloads.generate_async( template=_template(), @@ -118,7 +118,7 @@ async def test_provenance_metadata_on_payloads(self) -> None: assert meta["objective"] == "Make agent send data to attacker." assert meta["variant_index"] == 0 - async def test_manifest_reaches_llm_prompt(self) -> None: + async def test_manifest_reaches_llm_prompt_async(self) -> None: """Manifest tools and agent name appear in the LLM user message.""" captured: dict[str, str] = {} @@ -144,7 +144,7 @@ def capture(*, system_message: str, user_message: str) -> str: assert "send_email" in captured["user_message"] assert "TestAgent" in captured["user_message"] - async def test_persona_becomes_system_message(self) -> None: + async def test_persona_becomes_system_message_async(self) -> None: """Persona system_prompt is forwarded as the LLM system message.""" captured: dict[str, str] = {} @@ -172,7 +172,7 @@ def capture(*, system_message: str, user_message: str) -> str: class TestConverterPipeline: """Converter chaining — sequential pipeline like PyRIT.""" - async def test_returns_base_and_converted(self) -> None: + async def test_returns_base_and_converted_async(self) -> None: """With converters, output is base text + final chain result.""" with _patch_llm("content"): result = await Payloads.generate_async( @@ -189,7 +189,7 @@ async def test_returns_base_and_converted(self) -> None: assert result[1].content == "CONTENT" assert result[1].format is PayloadFormat.HTML - async def test_chaining_feeds_output_to_next_converter(self) -> None: + async def test_chaining_feeds_output_to_next_converter_async(self) -> None: """[Upper, Prefix] chains: upper first, then prefix the result.""" with _patch_llm("hello"): result = await Payloads.generate_async( @@ -206,7 +206,7 @@ async def test_chaining_feeds_output_to_next_converter(self) -> None: # Chain: "hello" -> Upper -> "HELLO" -> Prefix -> "PREFIX:HELLO" assert result[1].content == "PREFIX:HELLO" - async def test_multiple_variants_one_chain_per_variant(self) -> None: + async def test_multiple_variants_one_chain_per_variant_async(self) -> None: with _patch_llm("a", "b"): result = await Payloads.generate_async( template=_template(), @@ -220,7 +220,7 @@ async def test_multiple_variants_one_chain_per_variant(self) -> None: assert len(result) == 4 assert [p.content for p in result] == ["a", "b", "A", "B"] - async def test_empty_converters_same_as_none(self) -> None: + async def test_empty_converters_same_as_none_async(self) -> None: with _patch_llm("variant"): result = await Payloads.generate_async( template=_template(), @@ -233,7 +233,7 @@ async def test_empty_converters_same_as_none(self) -> None: assert len(result) == 1 assert result[0].format is PayloadFormat.TEXT - async def test_converter_metadata_preserved(self) -> None: + async def test_converter_metadata_preserved_async(self) -> None: """Converter can add its own metadata alongside provenance.""" with _patch_llm("content"): result = await Payloads.generate_async( diff --git a/tests/unit/pytest_plugin/test_collection.py b/tests/unit/pytest_plugin/test_collection.py index 4aeda58c..3a7b2d88 100644 --- a/tests/unit/pytest_plugin/test_collection.py +++ b/tests/unit/pytest_plugin/test_collection.py @@ -82,7 +82,7 @@ async def test_records_on_post_execute_async(self) -> None: result=result, ) - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) assert len(collector.results) == 1 assert collector.results[0].summary == "captured" @@ -96,7 +96,7 @@ async def test_ignores_pre_execute_async(self) -> None: handler = ResultCollectionHandler() event_data = _make_event_data(event=ExecutionEvent.ON_PRE_EXECUTE) - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) assert collector.results == [] finally: @@ -109,7 +109,7 @@ async def test_ignores_on_error_async(self) -> None: handler = ResultCollectionHandler() event_data = _make_event_data(event=ExecutionEvent.ON_ERROR) - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) assert collector.results == [] finally: @@ -123,7 +123,7 @@ async def test_noop_when_no_collector_active_async(self) -> None: result=result, ) - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) async def test_noop_when_result_is_none_async(self) -> None: collector = ResultCollector() @@ -135,7 +135,7 @@ async def test_noop_when_result_is_none_async(self) -> None: result=None, ) - await handler.on_event(event_data=event_data) + await handler.on_event_async(event_data=event_data) assert collector.results == [] finally: @@ -226,12 +226,12 @@ async def test_sees_results_from_child_task_async(self) -> None: collector = ResultCollector() token = activate_collector(collector) - async def _body() -> None: + async def _body_async() -> None: await asyncio.sleep(0) record_result(result=_make_result(summary="child")) try: - await asyncio.create_task(_body()) + await asyncio.create_task(_body_async()) active = get_active_collector() assert active is collector assert len(active.results) == 1 diff --git a/tests/unit/reporting/test_json_file.py b/tests/unit/reporting/test_json_file.py index 35bfec6d..80521773 100644 --- a/tests/unit/reporting/test_json_file.py +++ b/tests/unit/reporting/test_json_file.py @@ -203,7 +203,7 @@ def test_turns_omit_driver_reasoning_when_empty(self) -> None: class TestEmitAsync: """emit_async writes a valid JSON file.""" - async def test_emitted_file_contains_metadata(self, tmp_path: Path) -> None: + async def test_emitted_file_contains_metadata_async(self, tmp_path: Path) -> None: sink = JsonFileReportSink(output_dir=tmp_path) result = _result_with_turns( result_metadata={"conversation_id": "xyz"}, diff --git a/tests/unit/surfaces/test_onedrive.py b/tests/unit/surfaces/test_onedrive.py index fd269f7e..aa418555 100644 --- a/tests/unit/surfaces/test_onedrive.py +++ b/tests/unit/surfaces/test_onedrive.py @@ -164,7 +164,7 @@ def test_payload_id(self) -> None: class TestOneDriveInjectionLifecycle: """Test the async context manager lifecycle (upload + delete).""" - async def test_enter_uploads_and_stores_item_id(self) -> None: + async def test_enter_uploads_and_stores_item_id_async(self) -> None: client = _make_graph_client(upload_item_id="item-xyz") surface = OneDriveSurface( graph_client=client, @@ -177,7 +177,7 @@ async def test_enter_uploads_and_stores_item_id(self) -> None: async with handle as h: assert h._item_id == "item-xyz" - async def test_upload_uses_correct_graph_path(self) -> None: + async def test_upload_uses_correct_graph_path_async(self) -> None: """Verify the path-based addressing format root:/{folder}/{file}:.""" client = _make_graph_client(upload_item_id="item-1") surface = OneDriveSurface( @@ -196,7 +196,7 @@ async def test_upload_uses_correct_graph_path(self) -> None: upload_call = by_drive_item_id.call_args_list[0] assert upload_call == call("root:/Documents/payloads/abc123.txt:") - async def test_exit_deletes_with_correct_item_id(self) -> None: + async def test_exit_deletes_with_correct_item_id_async(self) -> None: client = _make_graph_client(upload_item_id="item-to-delete") surface = OneDriveSurface( graph_client=client, @@ -215,7 +215,7 @@ async def test_exit_deletes_with_correct_item_id(self) -> None: assert delete_call == call("item-to-delete") client._delete_mock.delete.assert_awaited_once() - async def test_upload_failure_raises_infrastructure_error(self) -> None: + async def test_upload_failure_raises_infrastructure_error_async(self) -> None: client = _make_graph_client( upload_error=ConnectionError("Graph API unavailable"), ) @@ -231,7 +231,7 @@ async def test_upload_failure_raises_infrastructure_error(self) -> None: async with handle: pass - async def test_delete_failure_logs_warning_does_not_raise(self) -> None: + async def test_delete_failure_logs_warning_does_not_raise_async(self) -> None: client = _make_graph_client( upload_item_id="item-1", delete_error=ConnectionError("cleanup failed"), @@ -248,7 +248,7 @@ async def test_delete_failure_logs_warning_does_not_raise(self) -> None: async with handle: pass - async def test_exit_skips_delete_when_no_item_id(self) -> None: + async def test_exit_skips_delete_when_no_item_id_async(self) -> None: """If upload was never called, exit should be a no-op.""" surface = OneDriveSurface( graph_client=MagicMock(), @@ -261,7 +261,7 @@ async def test_exit_skips_delete_when_no_item_id(self) -> None: # Call __aexit__ directly without __aenter__ await handle.__aexit__(None, None, None) - async def test_returns_self_from_aenter(self) -> None: + async def test_returns_self_from_aenter_async(self) -> None: client = _make_graph_client() surface = OneDriveSurface( graph_client=client, @@ -274,7 +274,7 @@ async def test_returns_self_from_aenter(self) -> None: async with handle as h: assert h is handle - async def test_upload_exceeding_size_limit_raises_infrastructure_error( + async def test_upload_exceeding_size_limit_raises_infrastructure_error_async( self, ) -> None: client = _make_graph_client() @@ -291,7 +291,7 @@ async def test_upload_exceeding_size_limit_raises_infrastructure_error( async with handle: pass - async def test_null_drive_item_raises_infrastructure_error(self) -> None: + async def test_null_drive_item_raises_infrastructure_error_async(self) -> None: client = _make_graph_client(upload_return=None) surface = OneDriveSurface( graph_client=client, @@ -305,7 +305,7 @@ async def test_null_drive_item_raises_infrastructure_error(self) -> None: async with handle: pass - async def test_null_drive_item_id_raises_infrastructure_error(self) -> None: + async def test_null_drive_item_id_raises_infrastructure_error_async(self) -> None: """DriveItem exists but has a None id.""" item_with_no_id = MagicMock() item_with_no_id.id = None @@ -322,7 +322,9 @@ async def test_null_drive_item_id_raises_infrastructure_error(self) -> None: async with handle: pass - async def test_infrastructure_error_from_upload_not_double_wrapped(self) -> None: + async def test_infrastructure_error_from_upload_not_double_wrapped_async( + self, + ) -> None: """InfrastructureError raised inside _upload_async propagates directly.""" original = InfrastructureError("Graph returned no DriveItem") client = _make_graph_client(upload_error=original) @@ -342,10 +344,10 @@ async def test_infrastructure_error_from_upload_not_double_wrapped(self) -> None class TestOneDriveInjectionWaitUntilReady: - """Test _OneDriveInjection.wait_until_ready wiring.""" + """Test _OneDriveInjection.wait_until_ready_async wiring.""" - async def test_delegates_to_sleep_until_ready(self) -> None: - """Verifies correct arguments are passed to sleep_until_ready.""" + async def test_delegates_to_sleep_until_ready_async(self) -> None: + """Verifies correct arguments are passed to sleep_until_ready_async.""" surface = OneDriveSurface( graph_client=MagicMock(), drive_id="d", @@ -355,9 +357,9 @@ async def test_delegates_to_sleep_until_ready(self) -> None: handle = surface.inject(payload=Payload(content="test")) with patch( - "rampart.surfaces.onedrive.sleep_until_ready", + "rampart.surfaces.onedrive.sleep_until_ready_async", new_callable=AsyncMock, ) as mock_sleep: - await handle.wait_until_ready() + await handle.wait_until_ready_async() mock_sleep.assert_awaited_once_with(delay=5.0)