BootcampCapstone · Deliverable 6

API smoke-check notes for the promo endpoint

Builds on Topic 9.

What you'll produce

A short set of Postman-style API smoke checks for ShopSphere's apply-promo-code endpoint: the request you send, the exact response you assert, and at least two negative cases (an invalid code and an expired code) — each with the status code, the key response fields, and a one-line pass/fail check anyone could re-run. You close with a few sentences on why testing the promo logic at the API level is faster and more stable than driving it through the checkout UI. This matters because it's the first artifact in your capstone where you test beneath the screen — proving the discount math and the rejection rules hold at the source, not just that the button looks right. It's the deliverable that shows a hiring manager you understand where manual QA grows toward automation: the same checks here become the first scripts an SDET would automate into a regression suite.

Instructions

  1. Pin down the contract first. From the requirements spec, write one line stating the endpoint: method, path, and the body it expects. For ShopSphere use POST /api/v1/cart/{cartId}/promo with a JSON body { "code": "<STRING>" }. Use this one contract everywhere you reference the endpoint — if your bug reports (Deliverable 4) and your smoke checks describe the same feature with two different paths or versions, a reviewer can't tell which one is real, and neither can a developer. Pinning a single canonical contract and reusing it verbatim is itself the QA skill here. If the spec is vague, note your assumption explicitly — a QA who states assumptions is doing the job.
  2. Set up the request once. In Postman (or the template), record the base URL for staging, the auth header your team uses (e.g., a bearer token for a test account), and a known cart you control. Reuse one cart with a fixed subtotal across every check so the discount math is verifiable — a moving subtotal makes a "wrong total" impossible to diagnose.
  3. Write the happy-path check. Send a valid, in-date promo code against that known cart. Record the expected HTTP status (200), and assert the specific response fields that prove the discount applied: the discount amount, the new total, and a success flag. Use real numbers you computed by hand (subtotal, percentage, expected total) so the check is falsifiable, not "looks right."
  4. Write negative case 1 — invalid code. Send a code that doesn't exist (FAKE99). Assert the status code (a 4xx, not 200), the error code/message, and — critically — that the cart total is unchanged. A silently-applied phantom discount is a revenue bug; check for it.
  5. Write negative case 2 — expired code. Send a code that exists but is past its end date — the canonical expired code SUMMER19 (expired 2026-05-31) from the Capstone fixtures. Assert it's rejected with the right status and message, and that no discount was applied. This is the exact failure the capstone tip warns about — an expired code accepted at checkout. (Watch the fixtures: SUMMER19 is the expired code; SUMMER20 is the active alias of SAVE20. Sending the wrong one is the kind of drift that makes the smoke suite test the opposite of what you meant.)
  6. Add 1–2 high-value extra checks that the UI would make slow or impossible to hit reliably: e.g., applying a code twice (should not stack/double-discount), or a code below its minimum-spend threshold. These are where API testing earns its keep.
  7. Include the empty-code check — send the endpoint a body with an empty string ({ "code": "" }). This is the exact payload that crashed the server with a 500 in your Deliverable 4 bug pack (BUG 2 / SHOP-1492), so your smoke suite must assert it now returns a clean 4xx validation error, not a 500. A smoke check that re-runs your worst already-filed defect is how the suite stops a regression from shipping silently. Assert the status, the validation error code, and that the total is untouched.
  8. For every check, write a one-line pass/fail assertion in plain language ("PASS if status == 200 AND discount == 4.40 AND total == 39.60"). That assertion is what a Postman test script — or your future automated suite — will encode.
  9. Write the "why API not UI" note (3–5 sentences). Ground it in Topic 9: API checks are faster (no browser, no page loads, runnable on every build) and more stable (no flaky selectors or layout changes), and they isolate the promo logic so a failure points at the rule, not the button. Name one thing the UI still must cover (e.g., the discount actually rendering on the order summary) so you show you know API testing complements UI testing, it doesn't replace it.
  10. Keep it copy-runnable. Anyone on the team should be able to paste your request, hit Send, and check your assertion. That re-runnability is the whole point of a smoke check.

Worked example

Feature under test: ShopSphere apply-promo-code, redesigned checkout release. Environment: Staging API — https://staging-api.shopsphere.com · build 2.4.0-rc3 · auth: Bearer {test_token} for QA test account qa+promo@shopsphere.com. (Same build as the UI deliverables; the API service runs on the staging API host while the UI runs on https://staging.shopsphere.com — both are the shared Capstone fixtures.) Endpoint contract (from spec): POST /api/v1/cart/{cartId}/promo · body { "code": "<STRING>" } · returns the updated cart with discount fields. Fixed test cart: cartId = cart_7f3a91 — one item, subtotal $22.00 (used for every check below so the math is verifiable). (Assumption, flagged for the dev: percentage discounts apply to subtotal before tax/shipping. Confirm.)

Known promo codes (the canonical Capstone fixtures — same codes as every other deliverable):

CodeTypeRuleStatus
SAVE2020% offmin spend $20Active, in-date
SUMMER1920% offmin spend $20Expired 2026-05-31
FAKE99Does not exist
BIG5050% offmin spend $50Active, in-date

Check 1 — Happy path: valid in-date code (SAVE20)

Request:

POST /api/v1/cart/cart_7f3a91/promo
Authorization: Bearer {test_token}
Content-Type: application/json

{ "code": "SAVE20" }

Expected response — 200 OK:

{
  "cartId": "cart_7f3a91",
  "appliedCode": "SAVE20",
  "discountAmount": 4.40,
  "subtotal": 22.00,
  "newTotal": 17.60,
  "success": true
}

Hand-computed: 20% of $22.00 = $4.40; new total $22.00 − $4.40 = $17.60. PASS if status == 200 AND success == true AND discountAmount == 4.40 AND newTotal == 17.60.


Check 2 — Negative: invalid / non-existent code (FAKE99)

Request body: { "code": "FAKE99" } Expected response — 422 Unprocessable Entity:

{
  "success": false,
  "errorCode": "PROMO_NOT_FOUND",
  "message": "This promo code is not valid.",
  "newTotal": 22.00
}

PASS if status == 422 AND success == false AND errorCode == "PROMO_NOT_FOUND" AND newTotal == 22.00 (total unchanged — no phantom discount applied).


Check 3 — Negative: expired code (SUMMER19)

Request body: { "code": "SUMMER19" } Expected response — 422 Unprocessable Entity:

{
  "success": false,
  "errorCode": "PROMO_EXPIRED",
  "message": "This promo code has expired.",
  "newTotal": 22.00
}

PASS if status == 422 AND errorCode == "PROMO_EXPIRED" AND discountAmount is absent/0 AND newTotal == 22.00. Note: this is the capstone's headline risk — an expired code accepted and discounted. If this returns 200 with a discount, file a High-severity bug; it's direct revenue loss.


Check 4 — Extra (API earns its keep): code applied twice — no stacking

Steps: send SAVE20 (Check 1 succeeds), then send SAVE20 again to the same cart. Expected response — 409 Conflict (or 200 with the discount unchanged, per spec):

{
  "success": false,
  "errorCode": "PROMO_ALREADY_APPLIED",
  "newTotal": 17.60
}

PASS if the second call does not reduce the total further — newTotal stays 17.60, not 14.08. (Driving this through the UI is slow and easy to get wrong; one extra API call proves the rule.)


Check 5 — Extra: below minimum-spend (BIG50 on a $22 cart, min $50)

Request body: { "code": "BIG50" } PASS if status == 422 AND errorCode == "PROMO_MIN_SPEND_NOT_MET" AND newTotal == 22.00. Confirms the min-spend rule is enforced server-side, not just hidden in the UI.


Check 6 — Regression guard: empty code must not 500 (the BUG 2 / SHOP-1492 payload)

This is the exact request that returned a 500 in the Deliverable 4 bug pack — same canonical contract, same endpoint, now re-run as a permanent smoke check so the crash can never silently come back.

Request:

POST /api/v1/cart/cart_7f3a91/promo
Authorization: Bearer {test_token}
Content-Type: application/json

{ "code": "" }

Expected response — 422 Unprocessable Entity (a clean validation error, not a 500):

{
  "success": false,
  "errorCode": "PROMO_CODE_REQUIRED",
  "message": "Enter a promo code.",
  "newTotal": 22.00
}

PASS if status == 422 (specifically not 500) AND errorCode == "PROMO_CODE_REQUIRED" AND newTotal == 22.00. If this returns 500, the SHOP-1492 fix has regressed — re-open it. This is the single most valuable line in the suite: it pins a real, already-observed Critical defect to a one-second check that runs on every build.


Why test this at the API level (not only the UI): These six checks run in under a second each with no browser, so they go into the smoke suite that runs on every staging build — UI runs of the same flow take minutes and need a logged-in session and a populated cart on screen. They're more stable: there's no fragile button selector or layout change to break them, so a red result means the promo rule actually broke, not that a CSS class got renamed. And they isolate the logic — Check 3 proves the expiry rule directly and Check 6 proves the empty-code crash stays fixed, pointing the developer at the rule rather than at the checkout page. (Check 6 also shows why one canonical contract matters: it tests the exact same POST /api/v1/cart/{cartId}/promo the bug report named, so "is it fixed?" has a single unambiguous answer.) API testing doesn't replace UI testing, though: the UI must still verify the discount renders correctly on the order summary and that the error message is actually shown to the shopper — things the API can't see. So I'd keep these six as the fast first-line smoke check and keep a thin layer of UI tests for what the customer actually sees.

Rubric

The app's AI scores the learner's submission against these criteria and gives feedback. Levels: Needs work (1) / Solid (2) / Excellent (3). Passing = every criterion at Solid or above.

  • Endpoint contract & request setup — 1: method/path/body unclear or missing, no environment, or a path/version that contradicts how the same endpoint is named in another deliverable · 2: states method, path, body, and staging environment/auth, consistent with the rest of the capstone · 3: precise single canonical contract (same method, path, and version used in the bug pack), plus a fixed known cart and explicitly flagged assumptions, so every check is reproducible and a developer reading both artifacts sees one endpoint, not two.
  • Happy-path check with verifiable assertion — 1: "should work," no concrete response · 2: states expected status and key response fields · 3: asserts specific fields with hand-computed real numbers (subtotal, discount, total) that make the check falsifiable.
  • Two negative cases (invalid + expired) — 1: fewer than two, or only "it errors" · 2: both present with correct error status and message · 3: both assert the right error code/message AND that the cart total is unchanged — catching the silent-discount revenue bug.
  • Pass/fail criteria are re-runnable — 1: vague, judgment-based · 2: each check has a clear pass condition · 3: each pass/fail is a precise boolean assertion a teammate (or an automated script) could encode and re-run unchanged.
  • Why-API-not-UI reasoning — 1: missing or just "it's better" · 2: names faster and more stable · 3: grounds faster/stable/isolated-logic in real specifics AND notes what UI testing must still cover, showing API and UI testing are complementary.
  • Coherence with the capstone feature — 1: generic, disconnected from ShopSphere · 2: uses the checkout/promo feature and its codes · 3: visibly continues the same thread (e.g., the expired-SUMMER19 risk, and an empty-code regression check on the same contract that re-runs BUG 2 / SHOP-1492 from the Deliverable 4 bug pack) and ties a failing check to a severity-rated bug.