Skip to content

feat: list stored deals over REST and make export read them - #84

Merged
Sirajmx merged 8 commits into
IABTechLab:mainfrom
numarasSigmaSoftware:feature/deal-list-route
Sep 30, 2026
Merged

Sirajmx merged 8 commits into
IABTechLab:mainfrom
numarasSigmaSoftware:feature/deal-list-route

Conversation

@numarasSigmaSoftware

Copy link
Copy Markdown
Contributor

Problem

Both booking paths persist deals under deal:<id>, but no route enumerates them. export_deals reads a deal_index key that nothing in the codebase writes, so GET /api/v1/deals/export always returns an empty list. Verified against a running instance on the CSV adapter: two deals booked through POST /api/v1/deals and POST /api/v1/deals/from-template, export still returned count: 0.

Change

  • Add GET /api/v1/deals, operator key required, with an optional status filter typed as the shared DealStatus wire enum and applied to the wire-mapped status, so ?status=booked returns template-booked deals stored as confirmed. Returns DealListResponse: the existing DealBookingResponse envelope per deal, count, and skipped, the ids of stored records that fail to serialize (logged with their id; one bad row no longer fails the whole list).
  • Scoping decision: the list is operator-only, the same posture as the rest of the admin surface. Buyers keep GET /api/v1/deals/{deal_id}. Export is unchanged, including its stored-status filter (confirmed / proposed / cancelled) that connectors already use.
  • Back the list with deal_service.list_deals(), which uses the storage base's existing deal:* scan. export_deals now reads the same set and the dead deal_index lookup is gone.
  • Regenerate docs/reference/endpoints.md; add the route to docs/api/overview.md; changelog entry under Unreleased.

Relies on #75 for the wire mapping of the template path's internal confirmed status. docs/api/openapi.json is not touched here: it is regenerated in #83, and this branch rebases onto that once merged and regenerates again so the list route lands in the document.

Verification

  • tests/unit/test_deal_list_route.py: route registration, list, wire-status filter (including the 422 on an internal name), operator gate (401 anonymous), skipped rows, empty store, non-matching filter, a real scan on a temp SQLite backend with an idempotency key in the store, and export after booking through both POST routes. Each behavior test was seen failing before its change.
  • tests/unit/test_route_shadowing.py: storage mock gained list_deals.
  • Full unit suite on a fresh database: all pass. On a reused working directory one pre-existing test fails, test_self_asserted_advertiser_identity_is_floored, because the suite leaves ad_seller.db behind; that is Unit suite leaves ./ad_seller.db behind, so a second consecutive local run can fail #82 and is not touched by this PR.

Not changed

No pagination on the list yet, since no other list route in the API pages either. Endpoint counts in the hand-written docs are bumped in #83; one further bump follows once both merge.

Note for maintainers, not part of this change

tests/unit/test_deal_list_route.py copies the sys.modules stub for ad_seller.flows.execution_activation_flow that test_route_shadowing.py, test_deal_booking_endpoints.py, and test_trust_tier_verification.py already carry. Each copy exists to dodge the cancel-scope leak tracked in #60. #66 landed as part 2 of that issue, so the four stub blocks may now be removable. Left untouched here; a separate cleanup PR should delete all four once the suite is confirmed green without them.

Both booking paths persist deals under deal:<id>, but no route enumerated
them: export_deals read a deal_index key that nothing writes, so
GET /api/v1/deals/export always returned an empty list.

- Add GET /api/v1/deals (optional ?status=) returning DealListResponse,
  a page of DealBookingResponse envelopes, registered with the other
  literal deal routes ahead of the {deal_id} catch-all.
- Back it with deal_service.list_deals(), which uses the storage
  backend's deal:* scan; export_deals now reads the same set.
- Regenerate docs/reference/endpoints.md.

Relies on IABTechLab#75 for the wire mapping of the template path's internal
"confirmed" status. docs/api/openapi.json is not touched here: the
committed file predates several routes, and a separate docs PR
regenerates it from the app.
@numarasSigmaSoftware

Copy link
Copy Markdown
Contributor Author

@atc964 @Sirajmx Can you take a look on this PR? I can provided further info if needed.

@Sirajmx Sirajmx 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.

Reproduced the defect live, independently: two real deals stored under deal:* keys, no
deal_index key set anywhere (nothing in the codebase writes it) — export_deals() returns
count: 0. Exact match to the PR's claim.

Fix verified live through the real route, not just the PR's own tests: anonymous caller with no
auth override at all gets 401 {"detail":"Authentication required"}; an operator caller with two
well-formed deals gets count: 2, skipped: []; ?status=booked correctly returns only the deal
whose internal status is confirmed — live proof the #75 wire-mapping dependency is actually
live on current main, not just assumed; ?status=confirmed (an internal-only name) correctly
422s, confirming the filter is typed against the wire enum, not a passthrough string. Also
incidentally verified the skipped mechanism for real — fed it two deals missing deal_type
(my fixture error, not intentional), both got reported in skipped instead of 500ing the whole
list.

New test file test_deal_list_route.py: 11/11 passed. Full suite: 1578 passed. Ruff clean.
Fast-forwards cleanly onto current main, zero conflicts.

Design checks: operator-only scoping for the list (vs. buyer-scoped GET /api/v1/deals/{deal_id})
is right — this is an enumerate-everyone's-records shape, same posture as the rest of the admin
surface. docs/api/openapi.json genuinely isn't touched here (confirmed, not in the diff),
correctly deferred to #83.

Cross-repo: zero impact, confirmed by grep — buyer-agent only ever calls
GET /api/v1/deals/{deal_id} and POST /api/v1/deals, never the new list route or /export, and
has no operator credential to call it with anyway. Approving.

@Sirajmx
Sirajmx merged commit eeaa191 into IABTechLab:main Sep 30, 2026
5 checks passed
@numarasSigmaSoftware
numarasSigmaSoftware deleted the feature/deal-list-route branch September 30, 2026 12:48
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