From 27ffccf63e6cade5e22f7287594be5e97e2ff296 Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Wed, 14 Oct 2026 15:58:02 +0800 Subject: [PATCH] fix: make the contract runs deterministic Fleetbase API - Create an Order keeps the FIRST stop for Set Order Destination instead of a random one. The activity cycle walks forward from the destination, so a later stop left every stop before it untouched and Complete an Order refused the order with "Not all waypoints completed" (or not, by luck). - Get Order Next Activity records an empty answer as order_flow_exhausted; it and Update Order Activity then skip (pm.execution.skipRequest) instead of posting to a completed order and collecting 400 "Order is already completed." on every surplus pass of the cycle. Fleetbase Storefront API - Retrieve a Customer and Query customers send the Customer-Token: both endpoints only return the signed-in customer (401 otherwise), which the descriptions now say. - The creation-code request uses creation_code_identity, a third identity with no account; the signed-in customer's identity answered 422 "already exists", and the new-customer identity must keep its seeded code as the newest. Without one it documents the 422 refusal. - Check Review Eligibility sets review_eligible from can_review, so Create a review creates one when the run has a completed order and Retrieve/Delete address it. --- .../Orders/Create an Order.request.yaml | 27 ++++++++++--------- .../Get Order Next Activity.request.yaml | 16 +++++++++++ .../Orders/Update Order Activity.request.yaml | 9 +++++++ .../Customer/Query customers.request.yaml | 5 +++- .../Customer/Retrieve a Customer.request.yaml | 5 +++- ...eate a new storefront custome.request.yaml | 15 ++++++++++- .../Check Review Eligibility.request.yaml | 12 ++++++++- 7 files changed, 72 insertions(+), 17 deletions(-) diff --git a/postman/collections/Fleetbase API/Orders/Create an Order.request.yaml b/postman/collections/Fleetbase API/Orders/Create an Order.request.yaml index 42deb3e..25e8b13 100644 --- a/postman/collections/Fleetbase API/Orders/Create an Order.request.yaml +++ b/postman/collections/Fleetbase API/Orders/Create an Order.request.yaml @@ -36,21 +36,22 @@ scripts: let cv = pm.environment; cv.set("order_id", json_response.id); - if (Array.isArray(json_response.payload.waypoints)) { - const randomWaypoint = getRandomElement(json_response.payload.waypoints); - - if (randomWaypoint) { - cv.set('waypoint_id', randomWaypoint.id); - } + // Set Order Destination points the order at {{waypoint_id}}, and the activity cycle + // (Get Order Next Activity / Update Order Activity) then walks forward from there. + // It has to be the FIRST stop: a later one leaves every stop before it untouched, + // and Complete an Order refuses the order with "Not all waypoints completed". A + // random pick here passed or failed by luck. + if (Array.isArray(json_response.payload.waypoints) && json_response.payload.waypoints.length) { + const firstWaypoint = json_response.payload.waypoints + .slice() + .sort((a, b) => (a.order || 0) - (b.order || 0))[0]; + cv.set('waypoint_id', firstWaypoint.id); } - function getRandomElement(arr) { - if (arr.length === 0) { - return undefined; // Return undefined if the array is empty - } - const randomIndex = Math.floor(Math.random() * arr.length); - return arr[randomIndex]; - } + // A new order starts a new activity cycle, so forget that an earlier order's flow + // ran out. + cv.unset('order_flow_exhausted'); + cv.unset('next_activity'); language: text/javascript examples: ./.resources/Create an Order.resources/examples order: 1000 diff --git a/postman/collections/Fleetbase API/Orders/Get Order Next Activity.request.yaml b/postman/collections/Fleetbase API/Orders/Get Order Next Activity.request.yaml index adf9a74..892da0b 100644 --- a/postman/collections/Fleetbase API/Orders/Get Order Next Activity.request.yaml +++ b/postman/collections/Fleetbase API/Orders/Get Order Next Activity.request.yaml @@ -13,6 +13,16 @@ body: type: json content: "" scripts: + - type: beforeRequest + code: |- + // A contract run cycles this request with Update Order Activity more times than an + // order can need, since how many activities a flow takes is not known up front. Once + // the flow is exhausted there is nothing left to look up, so the surplus passes are + // skipped rather than sent: a skipped request is neither a pass nor a failure. + if (pm.environment.get('order_flow_exhausted')) { + pm.execution.skipRequest(); + } + language: text/javascript - type: afterResponse code: |- // The response is the list of activities available from the order's current point in @@ -22,6 +32,12 @@ scripts: if (Array.isArray(json_response) && json_response.length) { pm.environment.set("next_activity", JSON.stringify(json_response[0])); + } else { + // An empty list means the order has reached the end of its flow: applying the + // last activity completed it. Update Order Activity would now answer 400 + // "Order is already completed.", so the cycle stops here. + pm.environment.set("order_flow_exhausted", "true"); + pm.environment.unset("next_activity"); } language: text/javascript examples: ./.resources/Get Order Next Activity.resources/examples diff --git a/postman/collections/Fleetbase API/Orders/Update Order Activity.request.yaml b/postman/collections/Fleetbase API/Orders/Update Order Activity.request.yaml index d7313a7..997d017 100644 --- a/postman/collections/Fleetbase API/Orders/Update Order Activity.request.yaml +++ b/postman/collections/Fleetbase API/Orders/Update Order Activity.request.yaml @@ -15,6 +15,15 @@ body: "skip_dispatch": false } scripts: + - type: beforeRequest + code: |- + // Nothing to apply once Get Order Next Activity found the flow exhausted: the body + // would not be JSON, and the API refuses a completed order with 400 "Order is + // already completed." Skipped, not failed — see Get Order Next Activity. + if (pm.environment.get('order_flow_exhausted') || !pm.environment.get('next_activity')) { + pm.execution.skipRequest(); + } + language: text/javascript - type: afterResponse code: |- // Applying an activity that completes the current stop advances the order to the diff --git a/postman/collections/Fleetbase Storefront API/Customer/Query customers.request.yaml b/postman/collections/Fleetbase Storefront API/Customer/Query customers.request.yaml index ed18bbd..421eddc 100644 --- a/postman/collections/Fleetbase Storefront API/Customer/Query customers.request.yaml +++ b/postman/collections/Fleetbase Storefront API/Customer/Query customers.request.yaml @@ -1,6 +1,9 @@ $kind: http-request description: |- - Returns storefront customers for the current company. Use query and pagination parameters to find matching customer records. + Returns the customer signed in with the `Customer-Token` header. A storefront key alone is refused with `401`: it does not list the company's customers. url: "{{base_url}}/{{api_prefix}}/{{namespace}}/customers" method: GET +headers: + - key: Customer-Token + value: "{{customer_token}}" order: 6000 diff --git a/postman/collections/Fleetbase Storefront API/Customer/Retrieve a Customer.request.yaml b/postman/collections/Fleetbase Storefront API/Customer/Retrieve a Customer.request.yaml index f42ae2c..58df8af 100644 --- a/postman/collections/Fleetbase Storefront API/Customer/Retrieve a Customer.request.yaml +++ b/postman/collections/Fleetbase Storefront API/Customer/Retrieve a Customer.request.yaml @@ -1,6 +1,9 @@ $kind: http-request description: |- - Retrieves a storefront customer by ID. + Retrieves a storefront customer by ID. Only the customer signed in with the `Customer-Token` header can be retrieved; a storefront key alone is refused with `401`. url: "{{base_url}}/{{api_prefix}}/{{namespace}}/customers/{{customer_id}}" method: GET +headers: + - key: Customer-Token + value: "{{customer_token}}" order: 3500 diff --git a/postman/collections/Fleetbase Storefront API/Customer/Setups a verification request to create a new storefront custome.request.yaml b/postman/collections/Fleetbase Storefront API/Customer/Setups a verification request to create a new storefront custome.request.yaml index b075c37..ba9a608 100644 --- a/postman/collections/Fleetbase Storefront API/Customer/Setups a verification request to create a new storefront custome.request.yaml +++ b/postman/collections/Fleetbase Storefront API/Customer/Setups a verification request to create a new storefront custome.request.yaml @@ -16,6 +16,19 @@ body: content: |- { "mode": "email", - "identity": "{{customer_identity}}" + "identity": "{{creation_code_identity}}" } +scripts: + - type: beforeRequest + code: |- + // The identity must not have an account yet. A contract run supplies one in + // creation_code_identity. Create a Customer's {{new_customer_identity}} will not do: + // the newest live code for an identity is the one that counts, and the code seeded + // for that identity has to stay the newest. Without one, the request is sent for the + // signed-in customer's own identity and documents the refusal instead. + if (!pm.environment.get('creation_code_identity')) { + pm.variables.set('creation_code_identity', pm.environment.get('customer_identity')); + pm.variables.set('expected_status', 422); + } + language: text/javascript order: 4000 diff --git a/postman/collections/Fleetbase Storefront API/Reviews/Check Review Eligibility.request.yaml b/postman/collections/Fleetbase Storefront API/Reviews/Check Review Eligibility.request.yaml index 3132cb2..b6f8f99 100644 --- a/postman/collections/Fleetbase Storefront API/Reviews/Check Review Eligibility.request.yaml +++ b/postman/collections/Fleetbase Storefront API/Reviews/Check Review Eligibility.request.yaml @@ -18,8 +18,18 @@ queryParams: scripts: - type: afterResponse code: |- + var json_response = pm.response.json(); + pm.test('Eligibility says whether the customer can review', function () { - pm.expect(pm.response.json().can_review).to.be.a('boolean'); + pm.expect(json_response.can_review).to.be.a('boolean'); }); + + // Create a review documents the refusal unless the environment says the customer + // has a completed order to review, which is exactly what this answer settles. + if (json_response.can_review === true) { + pm.environment.set('review_eligible', 'true'); + } else { + pm.environment.unset('review_eligible'); + } language: text/javascript order: 1500