feat(examples): sync_accounts + list_accounts with billing_entity write-only guard wired#403
Draft
feat(examples): sync_accounts + list_accounts with billing_entity write-only guard wired#403
Conversation
…te-only guard wired Implements the two missing account-management methods on V3ReferenceSeller in the v3 reference seller example, satisfying issue #377. * sync_accounts — upserts AccountRow (stores full billing_entity including bank details for invoicing); echoes billing_entity back with bank stripped via project_business_entity_for_response before returning SyncAccountsSuccessResponse. Natural upsert key is brand.domain:operator; adopters with an existing ID scheme should replace this derivation. dry_run is intentionally ignored — response always carries dry_run=False to avoid lying to the caller about what was committed. * list_accounts — queries AccountRow scoped to the authenticated buyer agent with optional status/sandbox filters; reconstructs adcp.types.Account via model_validate (coerces billing_entity dict → BusinessEntity) then projects through project_account_for_response so bank is never serialised on the wire. Both methods use the module-level current_tenant import and guard ctx.buyer_agent before querying. They re-query BuyerAgentRow by agent_url to resolve the FK (ctx.buyer_agent.ext doesn't carry the surrogate id — see buyer_registry). Adds two smoke tests that drive the full method path through inline _StubSession fakes and assert bank absent / legal_name present in model_dump(mode="json") output, matching the existing no-PG test philosophy. Closes #377 https://claude.ai/code/session_01N51oUL2dxvvaVqe2xcjTuf
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #377
Implements the two missing account-management methods on
V3ReferenceSellerin the v3 reference seller example. TheAccount.billing_entitycolumn already stores full bank details for invoicing — this PR wires the existingproject_business_entity_for_response/project_account_for_responsehelpers so the write-only guard is enforced on every response path, not just architectural.Changes
examples/v3_reference_seller/src/platform.pysync_accounts— upsertsAccountRow(persists fullbilling_entityincluding bank); echoesbilling_entityback withbankstripped viaproject_business_entity_for_response; returnsSyncAccountsSuccessResponse. Natural upsert key is{brand.domain}:{operator}— comments explain the separator safety and tell adopters to replace this with their own stable key.dry_runis intentionally ignored (reference impl always commits);dry_run=Falseis returned to avoid lying to the caller about what was persisted.list_accounts— queriesAccountRowscoped to the authenticated buyer agent with optionalstatus/sandboxfilters; reconstructsadcp.types.Accountviamodel_validate(coercesbilling_entityJSON dict →BusinessEntityPydantic object, soproject_account_for_responsecan callmodel_copy()safely); returnsListAccountsResponse.Both methods re-query
BuyerAgentRowbyagent_urlto resolve the FK —ctx.buyer_agent.extdoesn't carry the DB surrogate id because_row_to_agentinbuyer_registry.pydiscardsrow.id. Comments at both call sites name the root cause.examples/v3_reference_seller/tests/test_smoke.pyTwo new async smoke tests using inline
_StubSessionfakes (consistent with the existing no-PG test philosophy). Both assertmodel_dump(mode="json")output so they test the serialised form, not just the Pydantic object attribute:test_sync_accounts_strips_bank_details— passes abilling_entitywith IBAN/BIC; asserts"bank"absent and"legal_name"present in the responsetest_list_accounts_strips_bank_details— stubs anAccountRowwith bank inbilling_entity; asserts sameWhat was tested
ruff check— clean (examples/ excluded from CI ruff run; verified explicitly)mypy examples/v3_reference_seller/src/platform.py --ignore-missing-imports— no new errors (3 pre-existing class-subclass errors in the file)pytest examples/v3_reference_seller/tests/ -v— 9/9 pass (2 new, 7 pre-existing)pytest tests/ -q— same baseline as main (1 pre-existing TLS test failing; unrelated to this PR)Pre-PR review
"name"to sync response dict; fixedsandboxnormalisation (None → False) to keep DB and wire consistent; changeddry_runecho fromreq.dry_runtoFalse(was a lie on the wire when session committed)Nits surfaced (not fixed — not blocking):
get_productsdocstring doesn't end with "Production adopters…" (pre-existing inconsistency; out of scope)billingwhenacct.billing is None(intentional no-op; acceptable for reference impl)Session: https://claude.ai/code/session_01N51oUL2dxvvaVqe2xcjTuf
Generated by Claude Code