Repository navigation
feat: list stored deals over REST and make export read them - #84
Conversation
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.
f37c7c6 to
2f36bc8
Compare
Sirajmx
left a comment
There was a problem hiding this comment.
Reproduced the defect live, independently: two real deals stored under
deal:*keys, no
deal_indexkey 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 gets401 {"detail":"Authentication required"}; an operator caller with two
well-formed deals getscount: 2,skipped: [];?status=bookedcorrectly returns only the deal
whose internal status isconfirmed— live proof the #75 wire-mapping dependency is actually
live on currentmain, 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 theskippedmechanism for real — fed it two deals missingdeal_type
(my fixture error, not intentional), both got reported inskippedinstead 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 currentmain, 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.jsongenuinely 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}andPOST /api/v1/deals, never the new list route or/export, and
has no operator credential to call it with anyway. Approving.
Problem
Both booking paths persist deals under
deal:<id>, but no route enumerates them.export_dealsreads adeal_indexkey that nothing in the codebase writes, soGET /api/v1/deals/exportalways returns an empty list. Verified against a running instance on the CSV adapter: two deals booked throughPOST /api/v1/dealsandPOST /api/v1/deals/from-template, export still returnedcount: 0.Change
GET /api/v1/deals, operator key required, with an optionalstatusfilter typed as the sharedDealStatuswire enum and applied to the wire-mapped status, so?status=bookedreturns template-booked deals stored asconfirmed. ReturnsDealListResponse: the existingDealBookingResponseenvelope per deal,count, andskipped, the ids of stored records that fail to serialize (logged with their id; one bad row no longer fails the whole list).GET /api/v1/deals/{deal_id}. Export is unchanged, including its stored-status filter (confirmed/proposed/cancelled) that connectors already use.deal_service.list_deals(), which uses the storage base's existingdeal:*scan.export_dealsnow reads the same set and the deaddeal_indexlookup is gone.docs/reference/endpoints.md; add the route todocs/api/overview.md; changelog entry under Unreleased.Relies on #75 for the wire mapping of the template path's internal
confirmedstatus.docs/api/openapi.jsonis 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 gainedlist_deals.test_self_asserted_advertiser_identity_is_floored, because the suite leavesad_seller.dbbehind; 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.pycopies thesys.modulesstub forad_seller.flows.execution_activation_flowthattest_route_shadowing.py,test_deal_booking_endpoints.py, andtest_trust_tier_verification.pyalready 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.