Skip to content

test: cover the payment-terms cross-field invariants (payment-terms.md) - #89

Open
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:test/payment-terms-conformance
Open

test: cover the payment-terms cross-field invariants (payment-terms.md)#89
vishkaty wants to merge 1 commit into
Universal-Commerce-Protocol:mainfrom
vishkaty:test/payment-terms-conformance

Conversation

@vishkaty

Copy link
Copy Markdown
Contributor

Adds a conformance module for the payment-terms capability that landed in ucp#602, covering the cross-field invariants the JSON schema cannot express. Offered in the #602 review thread (ucp#692 discussion) once the shape settled.

What it checks (all against the raw wire payload, following the totals_test.py stance):

Test Invariant Spec anchor (payment-terms.md)
selected_term_names_an_offered_term terms non empty when present; selected_term_id present and names exactly one terms[].id Where terms is present it MUST contain at least one term, and selected_term_id MUST name one of them
term_ids_unique_within_checkout terms[].id unique A Business MUST make terms[].id unique within a Checkout
selected_term_schedules_sum_to_checkout_total selected term schedule amounts sum to the checkout total For the selected term, the Business MUST ensure that sum equals the checkout total
selecting_each_term_recomputes_the_checkout each selection is honored, or the change is reported with a payment_term_changed warning — including when the requested term stays selected but is rewritten in place (schedules, due dates, amounts); the responses selected term always resolves and keeps the sum invariant; selections drawn from the latest response terms[] a business resolving a selection conflict MUST report the change as a payment_term_changed warning, and MUST also report it when changing the selected term in place without naming a different term; a Platform can therefore detect a changed selection from the code alone
unresolvable_selection_is_not_silently_substituted an unknown selected_term_id is either rejected or answered with a payment_term_changed warning, never a silent substitute; a control update with a valid id must succeed first so a 4xx is attributable to the id MUST NOT silently substitute a term, and MUST report the change as a payment_term_changed warning in messages[]
accepted_term_travels_to_the_order order carries payment.accepted_term for the term selected once the checkout REACHED ready_for_complete (skips honestly if readiness is not observed); its schedules sum to the order total at creation; asserts on the embedded order or follows the id to GET /orders/{id} (the order_test pattern) Carry the accepted term onto the Order; MUST ensure its schedule amounts sum to the Order total; the term is already agreed before ready_for_complete
due_at_when_present_is_rfc3339 every stated due_at matches the RFC 3339 shape (lowercase t/z and leap seconds accepted per the RFC; naive datetimes without a UTC offset rejected as not absolute) with calendar fields range checked due_at is an absolute RFC 3339 date-time

Conformant variability is accepted, not just the happy path. A business that answers a valid selection by keeping the previous term with a payment_term_changed warning (a selection conflict), changes its default term during fulfillment updates (warned), embeds only an order reference at completion, changes which terms are offered across responses, or rejects an unresolvable selection with a 4xx instead of warning: all pass. Each of these is exercised as its own verification mode.

Gating and current CI behavior. The module gates on dev.ucp.shopping.payment_terms in /.well-known/ucp (the discount_test.py pattern) and additionally skips when the driven checkout carries no terms, since offering terms on a given checkout is a business decision. Against the current sample server the module skips cleanly (verified against samples main); the checks activate the day a business or the reference advertises the capability.

Verification. Exercised against a minimal golden business implementing the extension: 2 answer postures and 6 additional spec-sanctioned behavior variants all PASS (including an in-place term rewrite that is correctly warned); 11 violation mutants across 14 matrix rows each fail the intended test (wrong schedule sum, duplicate term ids, dangling and missing selected_term_id, ignored selection, silent substitution, missing and mis-summing accepted_term with both full and reference-only order embeds, malformed and naive due_at). Broad violations (a dangling selection, duplicate ids) legitimately fail every test that resolves the selected term; the selection, silence, order, and due_at tests each also have a surgical killer that fails them alone. Ruff lint and format clean per the repo config.

Notes and limits.

  • Assertions are wire level dict reads, so nothing here depends on the SDK carrying payment_terms models.
  • Not asserted: that checkout terms are absent from the order (stated by the spec but additionalProperties true makes an extra field schema legal rather than a MUST violation); the disclosure travel MUSTs (they need a business with a disclosure bearing term to drive; follow-up once a reference implements one); the totals difference entry MUST (whether a term changes what the purchase costs is not observable server-agnostically); selected_term_id absent when terms absent (unreachable given the no-terms skip).
  • A business that churns term ids on every response without reporting payment_term_changed would still fail the selection test; the spec's own detection-from-the-code-alone sentence is read as making the warning the required signal for any changed selection.

@damaz91 damaz91 added the status:needs-triage Signal that the PR is ready for human triage label Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:needs-triage Signal that the PR is ready for human triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants