Skip to content

docs(port): add port review checklist for HTTP/test/UX parity pitfalls#1357

Merged
mergify[bot] merged 1 commit intomainfrom
devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e
May 6, 2026
Merged

docs(port): add port review checklist for HTTP/test/UX parity pitfalls#1357
mergify[bot] merged 1 commit intomainfrom
devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e

Conversation

@jd
Copy link
Copy Markdown
Member

@jd jd commented May 5, 2026

The 2026.5.5.x release shipped a ci scopes-send regression
(fixed in #1356): the Rust port deserialized the response body via
let _: serde_json::Value = client.post(...) while the Python
original ignored the body, and the Mergify endpoint returns an
empty body on success. Value still requires valid JSON, so the
Rust version surfaced parse response JSON: error decoding response body to every user once it shipped. Test mocks used
set_body_json(json!({})) instead of an empty body, so the bug
hid through unit tests too.

The fix is structural — split the HTTP client so callers pick
post (parses) vs post_no_response (drops) up front — but
the bug class is broader than this one command. Capturing the
lessons in AGENTS.md so the next porter (human or assistant) walks
through them before opening a PR.

Three categories of pitfall, all observed during the port so far:

  1. HTTP response handling — match the Python original. If
    Python ignores the response, Rust must use post_no_response
    etc.; if Python parses, optional/nullable fields must be
    Option<T> + #[serde(default)].

  2. Test fixtures — mirror the real server. ResponseTemplate
    bodies must match what the endpoint actually returns; an empty
    body is not the same as json!({}).

  3. UX parity with click — char-counted validators use
    chars().count() (not len()); confirmation prompts go to
    stderr; resolve all required state before prompting.

Audit of existing/in-flight ports against the checklist:

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

@jd
Copy link
Copy Markdown
Member Author

jd commented May 5, 2026

This pull request is part of a Mergify stack:

# Pull Request Link
1 docs(port): add port review checklist for HTTP/test/UX parity pitfalls #1357 👈
2 feat(rust): add mergify_core::auth with gh and git-config fallbacks #1364
3 feat(rust): port queue pause and unpause to native Rust #1352
4 feat(rust): port ci git-refs and ci queue-info to native Rust #1353
5 feat(rust): port queue status to native Rust #1359
6 test: derive native queue commands from the binary, not a hardcoded list #1366

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 5, 2026

Merge Protections

Your pull request matches the following merge protections and will not be merged until they are valid.

🟢 🤖 Continuous Integration

Wonderful, this rule succeeded.
  • all of:
    • check-success=ci-gate

🟢 👀 Review Requirements

Wonderful, this rule succeeded.
  • any of:
    • #approved-reviews-by>=2
    • author = dependabot[bot]
    • author = mergify-ci-bot
    • author = renovate[bot]

🟢 Enforce conventional commit

Wonderful, this rule succeeded.

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?:

🟢 🔎 Reviews

Wonderful, this rule succeeded.
  • #changes-requested-reviews-by = 0
  • #review-requested = 0
  • #review-threads-unresolved = 0

🟢 📕 PR description

Wonderful, this rule succeeded.
  • body ~= (?ms:.{48,})

@mergify mergify Bot requested a review from a team May 5, 2026 11:28
@jd jd marked this pull request as ready for review May 5, 2026 12:27
@jd jd force-pushed the devs/jd/worktree-rust-port/drop-port-status-toml-inventory-favor-port-delete--77522cb9 branch from 549a14d to 857f9fc Compare May 5, 2026 13:13
@jd jd force-pushed the devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e branch from ed2b8b5 to 6b1d62e Compare May 5, 2026 13:13
@jd
Copy link
Copy Markdown
Member Author

jd commented May 5, 2026

Revision history

# Type Changes Reason Date
1 initial ed2b8b5 2026-05-05 13:13 UTC
2 rebase ed2b8b5 → 6b1d62e 2026-05-05 13:13 UTC
3 rebase 6b1d62e → d8f08a0 2026-05-05 19:59 UTC
4 rebase d8f08a0 → 2b149f4 2026-05-06 10:57 UTC

@mergify mergify Bot had a problem deploying to Mergify Merge Protections May 5, 2026 13:13 Failure
jd added a commit that referenced this pull request May 5, 2026
The Rust binary now serves ``mergify queue status`` natively. The
Python implementation (``mergify_cli/queue/cli.py:status`` plus the
batch/scope/topology helpers it depended on) is removed in the
same PR — the port-and-delete rule we adopted in #1322 keeps a
single live copy of every command.

## What ports

``mergify queue status [-r REPO] [-t TOKEN] [-u URL] [-b BRANCH]
[--json]``:

1. Resolves repository / token / API URL via the shared
   ``mergify_queue::auth`` resolver introduced in #1352.
2. Fetches ``GET /v1/repos/<repo>/merge-queue/status``, optionally
   with ``?branch=<branch>`` (URL-encoded via
   ``url::form_urlencoded::byte_serialize``).
3. With ``--json``: pretty-prints the raw response. The schema is
   Mergify's API contract, not this CLI's, so we deserialize into
   ``serde_json::Value`` and emit verbatim — unknown fields and
   future schema additions survive the round trip.
4. Without ``--json``: deserializes into a typed ``StatusView``
   that uses ``#[serde(default)] Option<…>`` for every field the
   Mergify API has historically treated as optional/nullable
   (matches the port checklist from #1357), then renders a
   header, an optional pause indicator, the batch tree (grouped
   by scope when there is more than one), and the waiting-PR
   list. Status icons (``● ◑ ◌ ✓ ✗ ◎ ⏳ ↻ ⏰ ❄``) and relative
   times (``5m ago`` / ``~1h``) match the Python implementation.

## Rendering departures

Python used Rich's ``Tree`` for batches. The Rust port emits
flat indented text instead — same data, simpler rendering. Both
are line-oriented and round-trip cleanly through pipes; the
fancy box-drawing was visual sugar that didn't survive piping
anyway.

## Tests (24 new, in mergify-queue::status)

- ``build_path`` covers no-branch, branch, and URL-encoding of a
  branch name with slashes + spaces (e.g. ``feature/foo bar``
  becomes ``feature%2Ffoo+bar`` in the query).
- ``relative_time`` covers seconds / minutes / hours / days,
  future prefix, and graceful empty-string return on a malformed
  timestamp (matches Python's "degrade rather than fail").
- ``topological_sort`` covers parents-before-children ordering
  and tolerance of ``parent_ids`` that reference missing batches.
- ``group_by_scope`` covers the ``[]`` → ``"default"`` fallback
  and multi-scope batches appearing under each scope they claim.
- ``status_icon`` covers known + unknown codes.
- End-to-end wiremock tests: empty queue, paused queue, batches +
  waiting PRs, multi-scope grouping, ``?branch=…`` query
  threading, JSON-passthrough preserving an ``extra_field``,
  and tolerance of a response that omits all optional fields.

## Wiring

- ``crates/mergify-queue/Cargo.toml``: adds ``chrono`` (relative
  time math), ``indexmap`` (scope groups in insertion order),
  promotes ``serde_json`` from dev to runtime (used for the
  ``serde_json::Value`` passthrough).
- ``crates/mergify-cli/src/main.rs``: registers the ``status``
  subcommand under ``QueueSubcommand``, threads the
  ``--branch``/``--json`` flags, dispatches to
  ``mergify_queue::status::run``. Adds ``status`` to
  ``looks_native``.
- ``mergify_cli/queue/api.py``: removes ``QueueStatusResponse``,
  ``QueueBatch``, ``QueuePause``, ``QueueChecksSummary``,
  ``QueueBatchStatus``, ``QueuePullRequest``,
  ``QueuePullRequestAuthor``, and ``get_queue_status`` — all
  now Rust-native. ``QueuePullResponse`` and friends stay for
  the still-shimmed ``queue show`` (next phase).
- ``mergify_cli/queue/cli.py``: removes the ``@queue.command
  status`` block and the helpers it owned (``STATUS_STYLES``,
  ``_status_text``, ``_batch_label``, ``_pr_label``,
  ``_topological_sort``, ``_group_batches_by_scope``,
  ``_print_batches``, ``_print_waiting_prs``).
  ``_relative_time`` stays — ``show`` still uses it.
- ``mergify_cli/tests/queue/test_cli.py``: deletes
  ``TestStatusCommand``, ``TestTopologicalSort``, and the
  ``_invoke_status`` helper. ``TestRelativeTime`` stays.

Workspace: 138 Rust tests green, 590 Python tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change-Id: I8cebcd325f05173dfa41083da2ec6516a6ec3a3f
jd added a commit that referenced this pull request May 5, 2026
The Rust binary now serves ``mergify queue status`` natively. The
Python implementation (``mergify_cli/queue/cli.py:status`` plus the
batch/scope/topology helpers it depended on) is removed in the
same PR — the port-and-delete rule we adopted in #1322 keeps a
single live copy of every command.

## What ports

``mergify queue status [-r REPO] [-t TOKEN] [-u URL] [-b BRANCH]
[--json]``:

1. Resolves repository / token / API URL via the shared
   ``mergify_queue::auth`` resolver introduced in #1352.
2. Fetches ``GET /v1/repos/<repo>/merge-queue/status``, optionally
   with ``?branch=<branch>`` (URL-encoded via
   ``url::form_urlencoded::byte_serialize``).
3. With ``--json``: pretty-prints the raw response. The schema is
   Mergify's API contract, not this CLI's, so we deserialize into
   ``serde_json::Value`` and emit verbatim — unknown fields and
   future schema additions survive the round trip.
4. Without ``--json``: deserializes into a typed ``StatusView``
   that uses ``#[serde(default)] Option<…>`` for every field the
   Mergify API has historically treated as optional/nullable
   (matches the port checklist from #1357), then renders a
   header, an optional pause indicator, the batch tree (grouped
   by scope when there is more than one), and the waiting-PR
   list. Status icons (``● ◑ ◌ ✓ ✗ ◎ ⏳ ↻ ⏰ ❄``) and relative
   times (``5m ago`` / ``~1h``) match the Python implementation.

## Rendering departures

Python used Rich's ``Tree`` for batches. The Rust port emits
flat indented text instead — same data, simpler rendering. Both
are line-oriented and round-trip cleanly through pipes; the
fancy box-drawing was visual sugar that didn't survive piping
anyway.

## Tests (24 new, in mergify-queue::status)

- ``build_path`` covers no-branch, branch, and URL-encoding of a
  branch name with slashes + spaces (e.g. ``feature/foo bar``
  becomes ``feature%2Ffoo+bar`` in the query).
- ``relative_time`` covers seconds / minutes / hours / days,
  future prefix, and graceful empty-string return on a malformed
  timestamp (matches Python's "degrade rather than fail").
- ``topological_sort`` covers parents-before-children ordering
  and tolerance of ``parent_ids`` that reference missing batches.
- ``group_by_scope`` covers the ``[]`` → ``"default"`` fallback
  and multi-scope batches appearing under each scope they claim.
- ``status_icon`` covers known + unknown codes.
- End-to-end wiremock tests: empty queue, paused queue, batches +
  waiting PRs, multi-scope grouping, ``?branch=…`` query
  threading, JSON-passthrough preserving an ``extra_field``,
  and tolerance of a response that omits all optional fields.

## Wiring

- ``crates/mergify-queue/Cargo.toml``: adds ``chrono`` (relative
  time math), ``indexmap`` (scope groups in insertion order),
  promotes ``serde_json`` from dev to runtime (used for the
  ``serde_json::Value`` passthrough).
- ``crates/mergify-cli/src/main.rs``: registers the ``status``
  subcommand under ``QueueSubcommand``, threads the
  ``--branch``/``--json`` flags, dispatches to
  ``mergify_queue::status::run``. Adds ``status`` to
  ``looks_native``.
- ``mergify_cli/queue/api.py``: removes ``QueueStatusResponse``,
  ``QueueBatch``, ``QueuePause``, ``QueueChecksSummary``,
  ``QueueBatchStatus``, ``QueuePullRequest``,
  ``QueuePullRequestAuthor``, and ``get_queue_status`` — all
  now Rust-native. ``QueuePullResponse`` and friends stay for
  the still-shimmed ``queue show`` (next phase).
- ``mergify_cli/queue/cli.py``: removes the ``@queue.command
  status`` block and the helpers it owned (``STATUS_STYLES``,
  ``_status_text``, ``_batch_label``, ``_pr_label``,
  ``_topological_sort``, ``_group_batches_by_scope``,
  ``_print_batches``, ``_print_waiting_prs``).
  ``_relative_time`` stays — ``show`` still uses it.
- ``mergify_cli/tests/queue/test_cli.py``: deletes
  ``TestStatusCommand``, ``TestTopologicalSort``, and the
  ``_invoke_status`` helper. ``TestRelativeTime`` stays.

Workspace: 138 Rust tests green, 590 Python tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change-Id: I8cebcd325f05173dfa41083da2ec6516a6ec3a3f
@jd jd force-pushed the devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e branch from 6b1d62e to d8f08a0 Compare May 5, 2026 19:58
@jd jd force-pushed the devs/jd/worktree-rust-port/drop-port-status-toml-inventory-favor-port-delete--77522cb9 branch from 857f9fc to 10698c0 Compare May 5, 2026 19:58
@mergify mergify Bot had a problem deploying to Mergify Merge Protections May 5, 2026 19:59 Failure
Base automatically changed from devs/jd/worktree-rust-port/drop-port-status-toml-inventory-favor-port-delete--77522cb9 to main May 6, 2026 08:49
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 6, 2026

@jd this pull request is now in conflict 😩

@mergify mergify Bot added the conflict label May 6, 2026
The 2026.5.5.x release shipped a ``ci scopes-send`` regression
(fixed in #1356): the Rust port deserialized the response body via
``let _: serde_json::Value = client.post(...)`` while the Python
original ignored the body, and the Mergify endpoint returns an
empty body on success. ``Value`` still requires valid JSON, so the
Rust version surfaced ``parse response JSON: error decoding
response body`` to every user once it shipped. Test mocks used
``set_body_json(json!({}))`` instead of an empty body, so the bug
hid through unit tests too.

The fix is structural — split the HTTP client so callers pick
``post`` (parses) vs ``post_no_response`` (drops) up front — but
the bug class is broader than this one command. Capturing the
lessons in AGENTS.md so the next porter (human or assistant) walks
through them before opening a PR.

Three categories of pitfall, all observed during the port so far:

1. **HTTP response handling** — match the Python original. If
   Python ignores the response, Rust must use ``post_no_response``
   etc.; if Python parses, optional/nullable fields must be
   ``Option<T>`` + ``#[serde(default)]``.

2. **Test fixtures** — mirror the real server. ``ResponseTemplate``
   bodies must match what the endpoint actually returns; an empty
   body is not the same as ``json!({})``.

3. **UX parity with click** — char-counted validators use
   ``chars().count()`` (not ``len()``); confirmation prompts go to
   stderr; resolve all required state before prompting.

Audit of existing/in-flight ports against the checklist:

- ``config validate``, ``config simulate`` (main): match
- ``ci scopes-send`` (main): regressed, fix in #1356
- ``queue pause`` / ``unpause`` (#1352): match
- ``ci git-refs`` / ``queue-info`` (#1353): no HTTP, no risk

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Change-Id: I1fd9757e7f0213b0aeecbb6eb52b59e689cdf05a
@jd jd force-pushed the devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e branch from d8f08a0 to 2b149f4 Compare May 6, 2026 10:57
@jd jd temporarily deployed to func-tests-live May 6, 2026 10:57 — with GitHub Actions Inactive
@mergify mergify Bot deployed to Mergify Merge Protections May 6, 2026 10:57 Active
@mergify mergify Bot removed the conflict label May 6, 2026
@mergify mergify Bot requested a review from a team May 6, 2026 12:02
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 6, 2026

Merge Queue Status

This pull request spent 14 minutes 17 seconds in the queue, including 13 minutes 50 seconds running CI.

Required conditions to merge

mergify Bot added a commit that referenced this pull request May 6, 2026
@mergify mergify Bot added the queued label May 6, 2026
mergify Bot added a commit that referenced this pull request May 6, 2026
@mergify mergify Bot merged commit 6d6a444 into main May 6, 2026
19 checks passed
@mergify mergify Bot deleted the devs/jd/worktree-rust-port/add-port-review-checklist-http-test-ux-parity--1fd9757e branch May 6, 2026 12:23
mergify Bot pushed a commit that referenced this pull request May 6, 2026
…1364)

Centralizes the `--token` / `--api-url` / `--repository` resolvers
into one module so every ported command (config simulate,
ci scopes-send, the queue commands that follow in this stack)
shares the same fallback behavior:

- ``resolve_token``: explicit → ``MERGIFY_TOKEN`` → ``GITHUB_TOKEN``
  → ``gh auth token`` (subprocess; missing-binary or non-zero-exit
  is treated as "no token from gh", same as Python's
  ``CommandError`` catch in ``utils.get_default_token``).

- ``resolve_repository``: explicit → ``GITHUB_REPOSITORY`` →
  ``git config --get remote.origin.url`` parsed via ``parse_slug``
  (handles HTTPS and SSH shapes, strips ``.git`` and trailing
  slashes).

- ``resolve_api_url``: explicit → ``MERGIFY_API_URL`` →
  default ``https://api.mergify.com``.

The ``gh`` and ``git`` fallbacks are best-effort: missing binaries
or non-zero exits propagate as "fall through to the next source"
rather than surfacing errors. Mirrors the Python implementation.

``mergify-config::simulate`` and ``mergify-ci::scopes_send`` are
refactored in this same commit to use the new module — their
local copies of the resolvers (which were missing the gh / git
fallbacks) are removed. The duplicate tests that lived in those
modules are dropped; the env-precedence and slug-parsing coverage
now lives once in ``mergify_core::auth::tests``.

13 new tests in ``mergify_core::auth::tests``: env-var precedence
for token/api-url/repository, default API URL, error message
mentions ``gh client``, slug parsing for HTTPS / SSH / dot-git /
trailing-slash / empty-owner / path-without-repo. The
subprocess-fallback paths themselves aren't unit-tested (they
shell out to real binaries); the
``resolve_token_error_message_mentions_gh`` test exercises the
"PATH points to a directory with no gh" case to confirm the
fallback degrades cleanly.

The queue ports later in the stack consume this module from the
start — there's no transition period where they ship with a
private auth resolver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Depends-On: #1357
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

3 participants