Skip to main content

Integration Testing

Integration testing is a way to check that your integration consumes the Partners API correctly. Working through the scenarios is a structured way to surface bugs in your own integration - pagination gaps, missed version updates, mishandled substitutions or payments - against known, scripted data, before they show up in your live operation.

Each scenario exercises one behaviour of the API. Which scenarios are relevant depends on how your integration uses it - for example, if you only ingest completed sales the lifecycle/cancellation case matters less, and if you don't reconcile payments those scenarios won't apply. Work through the ones that match how your integration actually behaves.

This page describes the scenarios on offer and how each is checked.

tip

New here? The reference client is a complete, runnable example that consumes the API correctly and can run these scenarios against the test environment in one command.

How it works

The suite is split into two tracks, because the two kinds of test are checked in different ways.

Track 1 - Connection and sync. These check that your integration talks to the API correctly: it authenticates, reads the order stream in order, remembers its place between runs, never processes the same order twice, and backs off when rate-limited. We check these for you - you don't submit anything. As your integration runs through the scenarios, the requests it makes pass through Scoffable, and we confirm from our side that they follow the rules. There's nothing to screenshot or send us; we just need you to run each scenario.

Track 2 - Order handling. These check that your integration does the right thing with an order once it has received it - for example voiding a cancelled order, recording the item that was actually delivered after a substitution, or reconciling a split payment. The result of this lands in your own system (your till, accounting platform, etc.), which we can't see - so for these you show us the result: process the scenario and share your system's resulting state (for example a screenshot or an export). Each scenario lists the expected outcome so you know what "correct" looks like.

Getting set up

1. Request a test token

These tests run against a test environment that returns scripted orders instead of real data. Access requires a test token, which is separate from your live token and cannot read live data.

To obtain one, contact Scoffable. Test tokens are issued manually and are time-limited; if yours expires, contact us again for a replacement.

2. Point your client at a scenario

Each scenario has a descriptive id (e.g. test-rate-limit, test-substitutions). You run a scenario by using its id as a path segment in front of the normal endpoint path, on the test host:

GET https://test-partners-api.scoffable.com/test-pagination/v1/orderUpdates
Authorization: Bearer YOUR_TEST_TOKEN

Everything after the scenario segment works exactly as in production - the same endpoints, response shape, pagination and order structure. In practice you only change two things: the base URL (to https://test-partners-api.scoffable.com/test-pagination) and the token.

Some scenarios simulate a failure partway through the stream - a rate-limit or a transient server error on a page request after the first - and then succeed when you retry the same request. These exercise your retry and back-off logic, so retry exactly as you would in production (waiting any Retry-After the response asks for).

note

The test environment returns canned data. Order ids, vendor ids, barcodes and amounts are illustrative and do not correspond to real orders. Monetary amounts are integer minor units (e.g. 2930 = £29.30) in the currency given in the payload. Timestamps are generated relative to the current time so the data always falls within the supported window. For the same reason, the time-window query parameters minAgeMinutes and fromTimestamp are accepted but ignored here - each scenario always serves its full canned stream.

3. Re-running a scenario

The stateful scenarios (rate limiting and transient errors) remember how far through the fault you are, per token - so the failure fires once, partway through, rather than on every request. To run one again from the start, begin a fresh stream: make a request with no pageId, and the scenario restarts. If you simply stop, the state also clears itself after a few minutes of inactivity. Every other scenario is stateless, so re-running is just requesting from the beginning again.


Track 1 - Connection and sync

We verify these from the requests your integration makes - run each scenario and let your integration behave exactly as it would in production.

test-authentication - Authentication

  • Simulates: a normal 200 order stream (data / hasMore / nextPageId).
  • We check: you send the token in the Authorization header (never the URL or logs) and parse the 200.

test-invalid-token - Rejected token

  • Simulates: 401 {"message": "Unauthorized"} on every request - as if the token were missing, expired or invalid. Your test token is valid; this scenario 401s regardless, so you can test handling without crafting a broken token.
  • We check: your system stops and reports an error rather than retrying in a tight loop. (Test tokens are issued manually, so there's nothing to re-authenticate against - surfacing the failure is the correct response.)

test-pagination - Reading the stream

  • Simulates: a large, multi-page backlog with an opaque nextPageId cursor.
  • We check that you:
    • start with no pageId and follow hasMore to the end;
    • persist nextPageId and resume from it (even after a restart);
    • never skip or double-process an order, even if a page repeats;
    • keep pageSize within 1-100 and poll at a sensible cadence.

test-rate-limit - Rate limiting

  • Simulates: a mid-stream page request returns 429 + Retry-After; the same request succeeds once you've waited (too soon → 429 again).
  • We check: you honour Retry-After, retry the same request, resume with no gap, and back off on repeats. (Rate Limits)

test-transient-error - Transient errors

  • Simulates: a mid-stream page request returns a transient 5xx; the same request succeeds on retry.
  • We check: you retry the failed request from your saved cursor - no gap, no full restart.

test-expired-page-id - Expired page id

  • Simulates: your first request (no pageId) returns a normal page, but the nextPageId it gives you is already expired - so your next request (the first one carrying that pageId) returns 404 {"message": "pageId not found"}.
  • We check: you recognise the 404 and restart cleanly from the beginning (a request with no pageId) rather than failing or treating it as fatal. (Pagination)
  • Implementation note: this test re-expires the cursor on every restart, so a client that restarts unconditionally would loop against it. As a defensive measure you may want to cap consecutive restarts and alert if the cap is hit (the reference client uses a MAX_RESTARTS guard). In production this is belt-and-braces - a cursor that keeps expiring straight after a clean restart would imply a bug, so you'd rarely hit the cap.

test-empty-stream - No orders yet

  • Simulates: a normal 200 response with an empty stream - { "data": [], "hasMore": false } and no nextPageId. This is the first-run state of a freshly-connected integration, or any vendor with no orders in the window.
  • We check: you treat an empty page as "caught up, zero orders" - finishing cleanly rather than erroring, retrying, or assuming at least one order is present.

Track 2 - Order handling

For each scenario, process the order(s) as you would in production and show us the resulting state of your system (e.g. a screenshot or export). Each scenario states the expected outcome.

test-order-lifecycle - Order status and lifecycle

  • Simulates: an order delivered across three successive polls - placed (poll 1) → accepted (poll 2) → cancelled (poll 3), each version the order's current latest at its poll - plus, in the final poll, a rejected, an accepted, and a placed-only order.
  • Expected outcome: record the sale when you see the order accepted, and reverse it when a later poll shows it cancelled; the standalone accepted order also counts as a sale; rejected and placed-only never do. Because each transition arrives as latestVersion: true, a client that skips superseded versions still sees every one.

test-versioning - Versioning

  • Simulates: one order as four versions (placedacceptedacceptedaccepted/delivered), the money changing between them - delivered across three successive polls: poll 1 returns v1, poll 2 returns the next two versions (v2, v3) that have appeared since, poll 3 returns v4. The latest version at each poll carries latestVersion: true.
  • Expected outcome: persist nextPageId between polls and resume from it, so each poll picks up the new versions that have arrived; they converge to a single, current record (not four); you end on the latest amount; reprocessing an applied version is idempotent. (Order Versioning)
  • Skipping superseded versions: latestVersion is a flag on the whole order, not on the page - true only on the version that is the order's current latest at the moment it's served, and false on any a newer one has already superseded. Poll 2 hands you v2 (latestVersion: false - v3 had already superseded it) and v3 (latestVersion: true). If you only track an order's current state, you can skip every latestVersion: false version and apply only the true ones: the stream still delivers each order's current-latest version, so you converge to the same result with fewer downstream writes - here you write v3 (then v4) and skip v2, avoiding writing v2's £34 only to immediately correct it to v3's £17. The reference client does this by default; run it with VERBOSE=1 to see skip order 200 v2 logged. Applying every version in order instead is equally correct - it just does the extra write.

test-out-of-order-versions - Out-of-order versions

  • Simulates: the same order delivered newer-version-first - the accepted v2 arrives before the older placed v1.
  • Expected outcome: you keep the newer version and ignore the late-arriving older one - the order does not revert to placed. Compare by version (or trust latestVersion), don't blindly apply whatever arrives last. (Order Versioning)
  • Note: in production an order's versions always arrive in order (oldest first), so you should never actually see this. The scenario delivers them out of order deliberately, to check your handling stays defensive if a stale version ever did arrive late.

test-items-and-options - Items, options and quantities

  • Simulates: a customised product (one option with a premium surcharge), an offer line, and a barcode on packaged products (the composite set meal has none). The first version is the full order; a later version has one item out of stock, so its quantityFulfilled drops below quantityOrdered (and the order total with it).
  • Expected outcome: correct name/quantity/price per line; the fulfilled quantity used where it differs; premium surcharges included; the offer treated as a discount, not a product; products mapped by barcode where present, and handled gracefully where it's absent.

test-substitutions - Substitutions

  • Simulates: the substitution emerging across versions - a first version is the vanilla order as placed, then a later version shows each out-of-stock original (now quantityFulfilled: 0) replaced by the delivered substitute, cross-linked via substitutionDetails (a one-to-one and a many-to-one).
  • Expected outcome: you re-read the later version and record the items actually delivered (the substitutes), not the originals, linked correctly. (Substitutions)

test-price-adjustments - Price adjustments

  • Simulates: across versions - a vanilla order, then a later version where the item is out of stock and substituted with a dearer one, plus a linked negative adjustment that price-matches it back down to the original price.
  • Expected outcome: apply the adjustment to the right item (the customer pays the original price); treat it as a correction, not a product. (Price Adjustments)

test-vouchers - Vouchers and promotions

  • Simulates: one order with both kinds of voucher at once - a vendor-funded 10% off promotion as a negative-priced item (−£2.40), and a £5.00 Scoffable voucher in customerPayments (collectedBy: scoffable) alongside £16.60 cash. The two act in different places:
    total    = Family Feast 2400  +  voucher item (−240)   = 2160
    payments = Scoffable voucher 500 + cash 1660 = 2160 ✓ reconciles
  • Expected outcome: the voucher item reduces the order total (takings); the Scoffable voucher payment is money received toward that total - the vendor still gets full value, so it must not be netted off as well (doing so double-counts it). (Vouchers and Promotions)

test-payments - Payments and reconciliation

  • Simulates: two orders covering the common payment shapes - one split across collectors (vendor cash + Scoffable voucher) and one paid online (card via Scoffable) partly offset by a Scoffable voucher. Each carries a deliveryFee and serviceFee, and its payments add up to total.
  • Expected outcome: each payment recorded with its collectedBy (scoffable / vendor); every order's payments add up to total, including the fees. (Customer Payments)

test-delivery - Delivery and collection

  • Simulates: a collection order, an Uber delivery (has deliveredAt), and a vendor delivery (no deliveredAt).
  • Expected outcome: collection isn't treated as delivery; provider differences handled; you don't block on a deliveredAt that may never arrive. (Delivery Information)

test-multi-location - Multiple locations

  • Simulates: two orders from two different vendor locations.
  • Expected outcome: each order is routed to the correct vendor by vendorId - orders from different locations don't get merged or mis-attributed.

Working through the scenarios

Work through the scenarios relevant to your integration. For the Track 1 scenarios you run, we confirm from the requests we observe that they follow the rules; for the Track 2 scenarios, compare your system's resulting state against the expected outcome listed for each. Anything that doesn't line up is a bug worth fixing before it reaches your live operation.

If you have any questions, contact Scoffable.