feat: add player_id param to jersey/checkout for multi-player parents (#256) #261

Merged
forgejo_admin merged 1 commit from 256-player-id-jersey-checkout into main 2026-03-30 21:43:23 +00:00
Contributor

Summary

Add optional player_id query parameter to jersey and checkout endpoints so parents with multiple players can specify which child they are ordering for. When omitted, defaults to parent.players[0] for backwards compatibility. Returns 403 when a player_id does not belong to the authenticated parent.

Changes

  • src/basketball_api/routes/jersey.py — Added _resolve_player() helper and player_id: int | None = Query(None) to jersey_player_info and jersey_checkout endpoints
  • src/basketball_api/routes/checkout.py — Added _resolve_player() helper and player_id: int | None = Query(None) to create_checkout_session endpoint; added Player import
  • tests/test_jersey.py — Added parent_with_two_players fixture and TestPlayerIdPlayerInfo / TestPlayerIdJerseyCheckout test classes (7 new tests)
  • tests/test_checkout.py — Added parent_with_two_players fixture and TestPlayerIdCheckoutCreateSession test class (4 new tests)

Test Plan

  • pytest tests/ -k "player_id or jersey or checkout" — 97 passed
  • pytest tests/ -x -q — 700 passed, 0 failed
  • ruff format and ruff check clean

Review Checklist

  • player_id is optional (None default) — backwards compatible
  • Validation checks player.parent_id == parent.id via list membership — no cross-parent access
  • 403 returned for wrong player_id
  • Default to parent.players[0] when player_id omitted
  • All 3 endpoints updated: jersey/player-info, jersey/checkout, checkout/create-session
  • 11 new tests covering explicit selection, default fallback, and 403 rejection
## Summary Add optional `player_id` query parameter to jersey and checkout endpoints so parents with multiple players can specify which child they are ordering for. When omitted, defaults to `parent.players[0]` for backwards compatibility. Returns 403 when a `player_id` does not belong to the authenticated parent. ## Changes - `src/basketball_api/routes/jersey.py` — Added `_resolve_player()` helper and `player_id: int | None = Query(None)` to `jersey_player_info` and `jersey_checkout` endpoints - `src/basketball_api/routes/checkout.py` — Added `_resolve_player()` helper and `player_id: int | None = Query(None)` to `create_checkout_session` endpoint; added `Player` import - `tests/test_jersey.py` — Added `parent_with_two_players` fixture and `TestPlayerIdPlayerInfo` / `TestPlayerIdJerseyCheckout` test classes (7 new tests) - `tests/test_checkout.py` — Added `parent_with_two_players` fixture and `TestPlayerIdCheckoutCreateSession` test class (4 new tests) ## Test Plan - `pytest tests/ -k "player_id or jersey or checkout"` — 97 passed - `pytest tests/ -x -q` — 700 passed, 0 failed - `ruff format` and `ruff check` clean ## Review Checklist - [x] `player_id` is optional (None default) — backwards compatible - [x] Validation checks `player.parent_id == parent.id` via list membership — no cross-parent access - [x] 403 returned for wrong player_id - [x] Default to `parent.players[0]` when player_id omitted - [x] All 3 endpoints updated: jersey/player-info, jersey/checkout, checkout/create-session - [x] 11 new tests covering explicit selection, default fallback, and 403 rejection ## Related Notes - Closes #256 - Parent: forgejo_admin/westside-landing#196 (spike: player self-service jersey ordering) - Story: WS-S18
feat: add player_id param to jersey/checkout for multi-player parents (#256)
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
400fecc745
Add optional player_id query parameter to jersey/player-info,
jersey/checkout, and checkout/create-session endpoints so parents with
multiple players can specify which child they are ordering for. When
omitted, defaults to the first player for backwards compatibility.
Returns 403 when a player_id does not belong to the authenticated parent.

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

QA Review -- PR #261

Summary

Adds optional player_id query parameter to 3 endpoints (jersey/player-info, jersey/checkout, checkout/create-session) so multi-player parents can target a specific child. Defaults to first player when omitted. Returns 403 for cross-parent access.

Findings

Correctness -- PASS

  • _resolve_player validates ownership by iterating parent.players (loaded via joinedload), no raw query bypass possible
  • 403 Forbidden is the correct status for authenticated-but-unauthorized access
  • Backwards compatibility preserved: player_id=None falls through to parent.players[0]

Security -- PASS

  • No cross-parent access: player must exist in the parent's loaded relationship
  • Error message includes player_id value but not sensitive data

Test Coverage -- PASS

  • 11 new tests across 3 test classes
  • Covers: explicit selection, default fallback, 403 rejection, Stripe metadata propagation
  • All 700 tests pass, ruff clean

Scope -- PASS

  • 4 files changed, all within issue #256 scope
  • No unrelated modifications

Nits (non-blocking)

  1. Duplicated _resolve_player: Identical 16-line function in both jersey.py and checkout.py. Could be extracted to a shared utility (e.g., basketball_api/utils/player_resolve.py). Not a blocker -- the function is small and the modules are independent. Defer to a cleanup ticket if desired.

VERDICT: APPROVED

## QA Review -- PR #261 ### Summary Adds optional `player_id` query parameter to 3 endpoints (`jersey/player-info`, `jersey/checkout`, `checkout/create-session`) so multi-player parents can target a specific child. Defaults to first player when omitted. Returns 403 for cross-parent access. ### Findings **Correctness** -- PASS - `_resolve_player` validates ownership by iterating `parent.players` (loaded via `joinedload`), no raw query bypass possible - 403 Forbidden is the correct status for authenticated-but-unauthorized access - Backwards compatibility preserved: `player_id=None` falls through to `parent.players[0]` **Security** -- PASS - No cross-parent access: player must exist in the parent's loaded relationship - Error message includes `player_id` value but not sensitive data **Test Coverage** -- PASS - 11 new tests across 3 test classes - Covers: explicit selection, default fallback, 403 rejection, Stripe metadata propagation - All 700 tests pass, ruff clean **Scope** -- PASS - 4 files changed, all within issue #256 scope - No unrelated modifications ### Nits (non-blocking) 1. **Duplicated `_resolve_player`**: Identical 16-line function in both `jersey.py` and `checkout.py`. Could be extracted to a shared utility (e.g., `basketball_api/utils/player_resolve.py`). Not a blocker -- the function is small and the modules are independent. Defer to a cleanup ticket if desired. ### VERDICT: APPROVED
forgejo_admin force-pushed 256-player-id-jersey-checkout from 400fecc745
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
to f5d680d9ba
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2026-03-30 21:41:47 +00:00
Compare
forgejo_admin deleted branch 256-player-id-jersey-checkout 2026-03-30 21:43:23 +00:00
Sign in to join this conversation.
No description provided.