Skip to content

feat(baseline-kit): JSON/API response snapshot helper (api-snapshot modality) - #2207

Merged
stranske merged 1 commit into
mainfrom
feat/baseline-kit-snapshot-helper
Jun 1, 2026
Merged

feat(baseline-kit): JSON/API response snapshot helper (api-snapshot modality)#2207
stranske merged 1 commit into
mainfrom
feat/baseline-kit-snapshot-helper

Conversation

@stranske

@stranske stranske commented Jun 1, 2026

Copy link
Copy Markdown
Owner

What

Adds an API/JSON response-snapshot helper to packages/app-baseline-kit/, unblocking the api-snapshot testing modality. The next consumer is Manager-Database (a FastAPI app), which will snapshot GET /managers-style JSON responses against a seeded DB.

golden.check_metrics only handles a flat dict[str, float|int] via the num_regression fixture (numpy/pandas). API responses are nested JSON, so this uses pytest-regressions' data_regression fixture instead — arbitrary YAML-able data, no numpy/pandas — keeping it cheap for lightweight services.

New public API (baseline_kit/snapshot.py, exported from __init__)

  • normalize_response(payload, *, exclude=(), sort_key=None) — return a snapshot-stable copy of a JSON-able payload (redaction + deterministic ordering); call directly to inspect/assert structure.
  • check_snapshot(data_regression, payload, *, exclude=(), sort_key=None, basename=None) — normalize then data_regression.check(...); re-bless with --force-regen.
  • response_to_payload(response) — duck-typed adapter for any object with .json() + .status_code (Starlette/FastAPI TestClient, httpx.Response) → {"status_code": ..., "json": <body>}. No fastapi/httpx dependency.

Normalization (order: redact → sort_key reorder → recursive key sort)

Input never mutated; tuple coerced to list.

exclude path syntax (matched nodes are dropped):

Form Example Matches
Bare key (no .) "id", "created_at" that key at any depth
Dotted path "meta.request_id" that exact location, anchored at root
Wildcard * "items.*.updated_at" updated_at in every element of top-level items
Wildcard * "data.*" every direct child of data

* consumes exactly one level (list index or dict key).

List ordering: preserved by default; pass sort_key={<path-to-list>: <key-fn>} to sort an order-unstable record list before snapshotting (or pre-sort in the adapter).

Design decisions to confirm

  • Bare key = match at any depth; dotted path = anchored at root. A bare "id"/"created_at" redacts that field wherever it nests (the common timestamp/autoincrement case); a dotted path like "meta.request_id" only removes that exact root-anchored location. Flagging in case you'd prefer all paths anchored.
  • Lists keep input order unless sort_key is given. Kept simple/correct; sort is opt-in per-path rather than guessing a key.

Files

  • Added: baseline_kit/snapshot.py, tests/test_snapshot.py, tests/test_snapshot/test_check_snapshot_round_trip.yml (golden)
  • Changed: baseline_kit/__init__.py (exports), pyproject.toml (0.1.0 → 0.2.0), README.md (api-snapshot section + exclude syntax)

Tests / lint

  • cd packages/app-baseline-kit && pip install -e . pytest-regressions && pytest -q14 passed (incl. a data_regression round-trip; covers bare/dotted/wildcard redaction, key sorting, list handling, sort_key reorder, no-mutation, top-level list, duck-typed adapter).
  • ruff check + ruff format --check clean (root config: line-length 100, py312).

No new heavy deps (no numpy/pandas/fastapi/httpx).

🤖 Generated with Claude Code

…odality)

Add baseline_kit/snapshot.py to unblock snapshotting nested JSON API
responses (e.g. FastAPI GET /managers) against pytest-regressions'
data_regression fixture -- no numpy/pandas, unlike golden.check_metrics.

Public API:
  * normalize_response(payload, *, exclude=(), sort_key=None) -- stable copy
  * check_snapshot(data_regression, payload, *, exclude, sort_key, basename)
  * response_to_payload(response) -- duck-typed TestClient/httpx adapter

Normalization redacts volatile fields (bare keys at any depth, anchored
dotted paths, and `*` wildcard segments), optionally reorders record lists
via sort_key, and recursively sorts dict keys for deterministic output.

Exports the new names from __init__, adds tests/test_snapshot.py (14 tests,
incl. a data_regression round-trip golden), documents the modality + exclude
path syntax in README, and bumps the package to 0.2.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 1, 2026 01:56
@stranske
stranske temporarily deployed to agent-standard June 1, 2026 01:56 — with GitHub Actions Inactive
@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Workflow source needed

PR #2207 needs either a linked GitHub issue or one valid non-issue Workflow Source before PR metadata automation can manage it safely.

Please do one of:

  • Add <!-- meta:issue:123 --> or a normal Closes #123 / Related to #123 line.
  • Check one Workflow Source option in the PR body.
  • Add a hidden marker such as <!-- workflow-source:local_request -->, <!-- workflow-source:manual_remote -->, <!-- workflow-source:review_followup -->, <!-- workflow-source:sync_campaign -->, or <!-- workflow-source:dependabot -->.
  • Add a workflow source label such as workflow:source-direct-pr, workflow:source-local-request, workflow:source-review-followup, workflow:source-sync, or workflow:no-automation.

Once a valid source is present, this warning will not be reposted.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b35367123c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

anchored, bare = _match_paths(exclude)
redacted = _redact(payload, anchored, bare)
if sort_key:
prepared = [(_split(path), fn) for path, fn in sort_key.items()]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Support sorting root list payloads

For APIs that return a bare top-level array, e.g. GET /managers -> [...], there is currently no string path that can register a sort key for the payload itself: _split() always returns at least one segment, while _apply_sort_keys() only sorts the current list when the path tail is empty. That leaves order-unstable top-level lists producing flaky snapshots even though normalize_response otherwise accepts top-level list payloads. Consider reserving an empty/root path and converting it to [] before applying sort keys.

Useful? React with 👍 / 👎.

Copilot AI left a comment

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.

Pull request overview

Adds an api-snapshot testing modality to app-baseline-kit so nested JSON/API responses can be golden-mastered via pytest-regressions' data_regression fixture (no numpy/pandas), complementing the existing flat-metric golden.check_metrics. Provides redaction (bare-key / dotted / wildcard paths), opt-in list reordering via sort_key, recursive key sort, and a duck-typed adapter for TestClient/httpx responses. Bumps the package to 0.2.0 and documents the new modality in the README.

Changes:

  • New baseline_kit/snapshot.py exposing normalize_response, check_snapshot, response_to_payload, exported from __init__.
  • Tests covering bare/dotted/wildcard redaction, key sorting, list ordering, sort_key, tuple coercion, no-mutation, top-level lists, scalar pass-through, duck-typed adapter, and a data_regression round-trip with golden YAML.
  • README adds an "api-snapshot modality" section and exclude-syntax table; pyproject.toml bumped 0.1.0 → 0.2.0.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/app-baseline-kit/baseline_kit/snapshot.py New normalization + snapshot helpers and adapter.
packages/app-baseline-kit/baseline_kit/init.py Re-exports the new public API.
packages/app-baseline-kit/tests/test_snapshot.py Unit tests for redaction, sorting, adapter, round-trip.
packages/app-baseline-kit/tests/test_snapshot/test_check_snapshot_round_trip.yml Golden snapshot for round-trip test.
packages/app-baseline-kit/README.md Documents the new modality + exclude syntax.
packages/app-baseline-kit/pyproject.toml Version bump 0.1.0 → 0.2.0.

Comment on lines +145 to +152
here = [fn for p, fn in sort_keys if not p]
descended = []
for index, value in enumerate(node):
token = str(index)
child = [(p[1:], fn) for p, fn in sort_keys if p and _seg_matches(p[0], token)]
descended.append(_apply_sort_keys(value, child))
if here:
descended = sorted(descended, key=here[0])
Comment on lines +110 to +125
if isinstance(node, (list, tuple)):
out_list: list[JSONValue] = []
for index, value in enumerate(node):
token = str(index)
child_anchored = []
dropped = False
for path in anchored:
if _seg_matches(path[0], token):
if len(path) == 1:
dropped = True
break
child_anchored.append(path[1:])
if dropped:
continue
out_list.append(_redact(value, child_anchored, bare))
return out_list
Comment on lines +188 to +195
def check_snapshot(
data_regression,
payload: JSONValue,
*,
exclude: Iterable[str] = (),
sort_key: SortKeys | None = None,
basename: str | None = None,
) -> None:
@agents-workflows-bot

agents-workflows-bot Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Automated Status Summary

Head SHA: 09dc85a
Latest Runs: ⏳ pending — Gate
Required contexts: Gate / gate, Health 45 Agents Guard / guard
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 93.12%
Baseline 85.00%
Delta +8.12%
Minimum 70.00%
Status ✅ Pass

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
src/cli_parser.py 81.8% 4
src/percentile_calculator.py 95.0% 1
src/aggregator.py 95.0% 2
src/__init__.py 100.0% 0
src/ndjson_parser.py 100.0% 0

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@stranske stranske added agents:keepalive Use to initiate keepalive functionality with agents autofix Opt-in automated formatting & lint remediation agent:retry Add to trigger agent retry after rate limit or pause labels Jun 1, 2026
@stranske
stranske merged commit ea8ab51 into main Jun 1, 2026
50 of 57 checks passed
@stranske
stranske deleted the feat/baseline-kit-snapshot-helper branch June 1, 2026 02:03
@stranske
stranske temporarily deployed to agent-standard June 1, 2026 02:04 — with GitHub Actions Inactive
@stranske
stranske temporarily deployed to agent-standard June 1, 2026 02:04 — with GitHub Actions Inactive
@agents-workflows-bot

Copy link
Copy Markdown
Contributor

🤖 Bot Comment Handler

  • Agent: codex
  • Bot comments to address: 4

The agent has been assigned to this PR to address the bot review comments.

Instructions for agent

  1. Implement suggested fixes that improve the code
  2. Skip suggestions that don't apply (note why in your response)

The bot comment handler workflow has prepared context in the artifacts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents:keepalive Use to initiate keepalive functionality with agents autofix Opt-in automated formatting & lint remediation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants