Skip to content

feat(e2e-testing): auth-server client#21169

Open
y3rsh wants to merge 8 commits intoedgefrom
teach/auth-client-demo
Open

feat(e2e-testing): auth-server client#21169
y3rsh wants to merge 8 commits intoedgefrom
teach/auth-client-demo

Conversation

@y3rsh
Copy link
Copy Markdown
Member

@y3rsh y3rsh commented Apr 1, 2026

Overview

This work adds and refines e2e-testing support for the robot auth-server HTTP API: a typed AuthClient, Pydantic response models under e2e-testing/automation/clients/auth_models/, is_alive() (probes GET /auth/settings with full envelope validation), and two walkthrough scripts:

  • e2e-testing/scripts/check_auth.py: Rich panels, pretty-printed models, and assertions on the OAuth error body for a deliberate bad password (HTTP 400, error: invalid_grant, non-empty error_description), aligned with auth-server/tests/integration/test_oauth_flows.tavern.yaml.
  • e2e-testing/scripts/check_auth_quiet.py: Same call sequence with no stdout - much easier to read the code but does the exact same thing as the other script.

Purpose

The purpose of this script and client is to show how we can start from the human-readable Redoc documentation for the auth server, understand the available API surface, and build a reusable client for it. In this case, we used an LLM to help generate a client that knows how to talk to the auth server’s endpoints and perform meaningful business actions against them. The included scripts are just lightweight convenience checks that exercise the client against the dev server, but that is not the primary long-term use case. The real goal is to have a reusable client that we can use for setup and end-to-end workflows on actual robots, including scripting common tasks like provisioning administrative users, regular users, and users with different scopes once builds are deployed to robots with the auth server. These clients are intended primarily for real robot setup and real robot end-to-end testing, not for expanding CI coverage against dev servers, since we already have Tavern tests covering that space and any additional CI-focused API testing should likely be added there.

There is some implementation complexity here, especially around Pydantic-based model validation, which can feel a little magical at first glance, but that is not the main thing to focus on in this PR. The important part is the overall structure of the client and the pattern it gives us for building reusable API clients that support scripts, setup flows, and testing against real hardware.

Manual: dev auth-server locally

  1. Install dependencies once (your usual monorepo robot Python setup, e.g. make setup-py, plus cd e2e-testing && make setup).

  2. Start the auth-server (default dev port 33950, matches the scripts):

    cd auth-server
    make dev

    Leave this running. (auth-server/Makefile sets dev_port to 33950 for make dev.)

  3. Loud script (Rich output), second terminal:

    cd e2e-testing
    uv run python scripts/check_auth.py localhost

y3rsh added 2 commits April 1, 2026 14:23
Introduces a small httpx + Pydantic client for OAuth2 ROPC, introspection,
settings, and user CRUD against the robot auth-server, plus an interactive
Rich CLI for manual verification.

Dependencies: httpx, pydantic, rich.
Made-with: Cursor
@y3rsh y3rsh requested review from alexjoel42 and skowalski08 April 1, 2026 20:43
@y3rsh y3rsh self-assigned this Apr 1, 2026
Copy link
Copy Markdown
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

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

Cool!

It sounds like this is just a quick demo, but here's a warning about using Pydantic for this, if/when we take it further.

Comment on lines +11 to +37
class SettingsData(BaseModel):
"""``data`` object for GET/PATCH/DELETE /auth/settings."""

model_config = _SETTINGS_CFG

max_number_of_login_attempts: int | None = Field(
default=5,
alias="maxNumberOfLoginAttempts",
)
password_reset_time: float | None = Field(default=None, alias="passwordResetTime")
password_complexity_minimum_length: int | None = Field(
default=None,
alias="passwordComplexityMinimumLength",
)
password_complexity_special_characters: bool | None = Field(
default=None,
alias="passwordComplexitySpecialCharacters",
)
idle_logout: float = Field(default=180.0, alias="idleLogout")
require_reason_for_interaction: bool = Field(
default=True,
alias="requireReasonForInteraction",
)
min_length_of_reason_for_interaction: int | None = Field(
default=None,
alias="minLengthOfReasonForInteraction",
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Because of Pydantic footguns regarding nullable vs. omittable vs. defaultable fields, you might want to avoid Pydantic BaseModels for this kind of work.

The semantics that we're trying to implement for these PATCH JSON APIs are:

  • Omitting a field from the PATCH request will leave its value unchanged.
  • Setting a field to null in the PATCH request will set its value to null. Semantically, this generally means something like "disabled." For example, setting maxNumberOfLoginAttempts: null means to disable the login attempt limit.
  • Setting a field to a regular value in the PATCH request, like 123, will of course just set it to that value.
  • GET will always return the actual current values for everything, populating them with defaults if they've never been set.

But if you use this model to generate your PATCH requests, you won't be able to leave a value unset. e.g. if you omit max_number_of_login_attempts, your request will transmit an explicit 5. If you set it to None, your request will transmit an explicit null.

It's tempting to try to fix that by fiddling with the default values, and with options like exclude_unset=True. In my experience, those fixes don't work for every kind of field (e.g. try a field that's either an int or is omitted), and even if they do, they're subtle and easily broken.

You might want to try modeling the requests and responses as TypedDicts with NotRequired fields, and use Pydantic to parse them. (I have not tried this but it seems like it should work.) We can't do that in our server code because we need real Pydantic models in order to generate the OpenAPI spec, but that's not a concern for you on the consuming side.

I would also recommend using separate types for requests and responses, and not encoding server-side defaults like max_number_of_login_attempts=5.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thank you for the detailed call out! Got things adjusted for this and documented the pattern. I think I like how it looks now a lot better.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.94%. Comparing base (d0a8de8) to head (1e9bfc6).
⚠️ Report is 54 commits behind head on edge.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #21169      +/-   ##
==========================================
- Coverage   57.19%   56.94%   -0.25%     
==========================================
  Files        3977     3977              
  Lines      326193   326229      +36     
  Branches    46378    46381       +3     
==========================================
- Hits       186568   185784     -784     
- Misses     139406   140226     +820     
  Partials      219      219              
Flag Coverage Δ
app 45.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.
see 129 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@SyntaxColoring SyntaxColoring left a comment

Choose a reason for hiding this comment

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

Thanks!

Comment thread e2e-testing/automation/clients/auth_models/settings.py Outdated
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.

2 participants