Skip to content

feat: full assertion certainty for all plugins (v0.5.0)#4

Merged
elijahr merged 5 commits intomainfrom
elijahr/full-assertion-certainty
Mar 6, 2026
Merged

feat: full assertion certainty for all plugins (v0.5.0)#4
elijahr merged 5 commits intomainfrom
elijahr/full-assertion-certainty

Conversation

@elijahr
Copy link
Copy Markdown
Contributor

@elijahr elijahr commented Mar 6, 2026

Summary

  • AutoAssertError: new runtime guard — mark_asserted() during record() raises immediately, preventing the auto-assert anti-pattern rather than silently passing tests
  • HttpPlugin: expanded from 5 to 7 fields; headers/body renamed to request_headers/request_body; response_headers and response_body added; HttpAssertionBuilder + assert_request().assert_response() chained helper
  • All StateMachine plugins (Socket, Database, Smtp, Popen, AsyncWebSocket, SyncWebSocket): named per-step details dicts replace generic {method, args, kwargs}; auto-assert removed; typed assertion helpers added on each proxy
  • DatabasePlugin: new connect step recorded; initial state changed from "connected" to "disconnected"
  • PopenPlugin: "init" step renamed to "spawn"; stream ops removed; _FakeStream retained as no-op
  • SubprocessPlugin: all four run fields required (command, returncode, stdout, stderr); both which fields required
  • RedisPlugin: auto-assert removed; all three fields required; assert_command() typed helper added
  • BasePlugin.assertable_fields(): changed from @abstractmethod to concrete default frozenset(interaction.details.keys())

Breaking Changes

  • HttpPlugin: headers=request_headers=, body=request_body=; two new required fields
  • SubprocessPlugin: run and which assertions now require all fields
  • RedisPlugin: interactions no longer auto-asserted; explicit assertions required
  • All StateMachine plugins: interactions no longer auto-asserted; explicit assertions required; named field keys replace generic arg tuples
  • PopenPlugin: step "init" renamed to "spawn", stream step sentinels removed

Test Plan

  • 573 tests pass (pytest tests/ -q)
  • Auto-assert runtime guard verified: AutoAssertError raised immediately when mark_asserted() called during record()
  • Certainty test: removing any assert_interaction() call causes teardown failure for all plugins
  • All existing tests updated to explicit full-field assertions

elijahr added 2 commits March 5, 2026 23:09
- Add AutoAssertError: raised immediately if mark_asserted() is called
  while record() is in progress, preventing auto-assert at runtime
- Add _recording.py shared ContextVar module (avoids circular import
  between _base_plugin and _timeline)
- BasePlugin.assertable_fields() is now a concrete default returning
  frozenset(interaction.details.keys()); no longer abstract
- StateMachinePlugin._execute_step() accepts details= dict and
  return_interaction= flag; removes mark_asserted() call
- SocketPlugin, DatabasePlugin, SmtpPlugin, PopenPlugin,
  AsyncWebSocketPlugin, SyncWebSocketPlugin: named per-step details
  dicts, typed assertion helpers (assert_connect, assert_send, etc.)
- DatabasePlugin: new connect step; initial state disconnected
- PopenPlugin: init renamed to spawn; _FakeStream retained as no-op
- RedisPlugin: auto-assert removed; all three fields required;
  assert_command() typed helper added
- SubprocessPlugin: run requires all four fields (command, returncode,
  stdout, stderr); which requires both fields (name, returns)
- HttpPlugin: 5 fields expanded to 7; headers/body renamed to
  request_headers/request_body; response_headers and response_body
  added; HttpAssertionBuilder and assert_request() chained helper added
- All tests updated with explicit assertions; 573 tests pass
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the assertion certainty across all plugins by enforcing explicit assertion of all recorded interaction fields and prohibiting automatic assertion. It introduces a new error type to catch auto-assertion anti-patterns at runtime and refactors several plugins to use named fields and dedicated assertion helpers, making tests more robust and explicit about expected interactions.

Highlights

  • AutoAssertError: Introduced a new runtime guard, AutoAssertError, which is raised immediately if mark_asserted() is called during record(), preventing the auto-assert anti-pattern.
  • HttpPlugin Enhancements: Expanded HttpPlugin interactions from 5 to 7 fields, renaming headers/body to request_headers/request_body and adding response_headers and response_body. A new HttpAssertionBuilder with assert_request().assert_response() chained helper was added for ergonomic assertions.
  • StateMachine Plugins Refactor: All StateMachinePlugin subclasses (Socket, Database, Smtp, Popen, AsyncWebSocket, SyncWebSocket) now use named per-step details dictionaries instead of generic {method, args, kwargs}. Auto-assertion has been removed, and typed assertion helpers have been added for each proxy.
  • DatabasePlugin Updates: The DatabasePlugin now records a connect step and its initial state has been changed from 'connected' to 'disconnected'.
  • PopenPlugin Changes: The PopenPlugin's 'init' step was renamed to 'spawn', and stream operations (stdin.write, stdout.read, stderr.read) were removed, with _FakeStream retained as a no-op.
  • SubprocessPlugin Assertion Requirements: Both run and which assertions in SubprocessPlugin now require all relevant fields to be asserted for completeness.
  • RedisPlugin Assertion Requirements: Auto-assertion has been removed from RedisPlugin interactions, requiring explicit assertions for all three fields (command, args, kwargs). A new assert_command() typed helper was added.
  • BasePlugin.assertable_fields() Update: The BasePlugin.assertable_fields() method changed from an @abstractmethod to a concrete default implementation returning frozenset(interaction.details.keys()), simplifying plugin development while maintaining assertion certainty.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • CHANGELOG.md
    • Added AutoAssertError to prevent auto-assert anti-pattern.
    • Added HttpPlugin.assert_request() chained builder for ergonomic HTTP assertions.
    • Added named per-step assertion helpers for all state-machine plugins.
    • Added redis_mock.assert_command() typed helper for Redis command assertions.
    • Updated DatabasePlugin to record a connect step and changed its initial state to 'disconnected'.
    • BREAKING: Renamed and expanded HttpPlugin interaction fields (headers to request_headers, body to request_body, added response_headers and response_body).
    • BREAKING: SubprocessPlugin now requires all fields in run and which assertions.
    • BREAKING: RedisPlugin interactions are no longer auto-asserted; explicit assertions are required, and all three fields are now mandatory.
    • BREAKING: All StateMachinePlugin subclasses now use named per-step fields and no longer auto-assert interactions.
    • BREAKING: Renamed PopenPlugin step 'init' to 'spawn' and removed stream operations.
    • Changed BasePlugin.assertable_fields() from @abstractmethod to a concrete default.
    • Eliminated auto-assert anti-pattern from StateMachinePlugin and RedisPlugin.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Fix 13 ruff violations: sort imports (I001) in _timeline.py and
test_timeline.py, remove bare f-string (F541) in popen_plugin.py,
remove quoted annotations (UP037) in websocket_plugin.py and
test_state_machine_plugin.py, and wrap long lines (E501) in http.py,
popen_plugin.py, smtp_plugin.py, and socket_plugin.py.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This is an excellent and substantial pull request that significantly strengthens the core value proposition of the library by enforcing full assertion certainty. The removal of auto-asserting behavior across all plugins, the introduction of the AutoAssertError runtime guard, and the move to explicit, named fields for interactions are all fantastic improvements. The changes are consistent, well-documented, and thoroughly tested. I have one suggestion to improve the API clarity of the new HttpAssertionBuilder.

Note: Security Review did not run due to the size of the PR.

Comment on lines +205 to +224
def assert_request(
self,
method: str,
url: str,
headers: dict[str, Any] | None = None,
body: str = "",
) -> "HttpAssertionBuilder":
"""Return an HttpAssertionBuilder pre-loaded with expected request fields.

Call ``.assert_response()`` on the returned builder to complete the
assertion with all seven fields.
"""
return HttpAssertionBuilder(
verifier=self.verifier,
sentinel=self._sentinel,
method=method,
url=url,
headers=headers if headers is not None else {},
body=body,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The parameter names headers and body in assert_request and HttpAssertionBuilder.assert_response are ambiguous, as they refer to request attributes in one method and response attributes in the other. This is inconsistent with the recent renaming of HttpPlugin interaction fields to request_headers, request_body, etc.

For better clarity and consistency, I suggest renaming these parameters throughout the HttpAssertionBuilder flow:

  • In assert_request: headers -> request_headers, body -> request_body.
  • In HttpAssertionBuilder.__init__: update to accept request_headers and request_body.
  • In HttpAssertionBuilder.assert_response: headers -> response_headers, body -> response_body.

This would make the chained API more intuitive. The example in README.md would also need to be updated to reflect this change.

Suggested change
def assert_request(
self,
method: str,
url: str,
headers: dict[str, Any] | None = None,
body: str = "",
) -> "HttpAssertionBuilder":
"""Return an HttpAssertionBuilder pre-loaded with expected request fields.
Call ``.assert_response()`` on the returned builder to complete the
assertion with all seven fields.
"""
return HttpAssertionBuilder(
verifier=self.verifier,
sentinel=self._sentinel,
method=method,
url=url,
headers=headers if headers is not None else {},
body=body,
)
def assert_request(
self,
method: str,
url: str,
request_headers: dict[str, Any] | None = None,
request_body: str = "",
) -> "HttpAssertionBuilder":
"""Return an HttpAssertionBuilder pre-loaded with expected request fields.
Call ``.assert_response()`` on the returned builder to complete the
assertion with all seven fields.
"""
return HttpAssertionBuilder(
verifier=self.verifier,
sentinel=self._sentinel,
method=method,
url=url,
request_headers=request_headers if request_headers is not None else {},
request_body=request_body,
)

elijahr added 2 commits March 6, 2026 01:18
Rename HttpAssertionBuilder parameters to match the interaction field
naming convention: headers -> request_headers, body -> request_body in
assert_request(); headers -> response_headers, body -> response_body in
assert_response(). Update tests and README example accordingly.
Revert the Gemini-suggested rename. assert_request(headers=, body=) and
assert_response(status, headers=, body=) are unambiguous in context --
the method name already implies request/response scope. The prefixed form
(request_headers=, response_body=) is redundant and breaks DRY.
@elijahr elijahr merged commit f915278 into main Mar 6, 2026
10 checks passed
@elijahr elijahr deleted the elijahr/full-assertion-certainty branch March 6, 2026 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant