Skip to content

DannChang/promo-code-assessment

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Promo Code Assessment

Using Nx to allow two projects to be run on the same repository

Installation

    npm install

Nx workspace with:

  • web: React app
  • api: Express API

Run application

npx nx serve web
npx nx serve api

Build

npx nx build web
npx nx build api

Manual Frontend Test Cases

Start both apps:

npx nx serve api
npx nx serve web

Open the frontend:

http://127.0.0.1:4200

Expected checks:

  • The page shows the product picker, cart items, promo code input, discount breakdown, and promo stats sections.
  • The frontend uses the Vite /api proxy and does not require hardcoded backend URLs in the browser.

Basic cart flow:

  • Add TRT, Sermorelin, Semaglutide, or Ozempic from the product cards.
  • Confirm cart rows appear with quantity controls and remove actions.
  • Confirm the discount breakdown updates after a short debounce instead of on every keystroke or click.

Promo code flow:

  • Apply valid promo codes such as SAVE20, FLAT10, FLASH30, and VIP15.
  • Confirm applied discounts appear in the breakdown.
  • Apply invalid or conflicting promo codes and confirm rejected reasons are shown in the UI.
  • Apply duplicate or lowercase promo codes and confirm they normalize and dedupe correctly.

Persistence:

  • Add items and promo codes, then refresh the page.
  • Confirm the cart and applied promo codes restore from localStorage.

Promo stats:

  • Confirm the promo stats section loads on page render.
  • Click the manual refresh button and confirm stats refresh successfully.
  • Apply promo codes successfully and confirm stats update after refresh / polling.

Rate limit behavior:

  • Trigger repeated cart recalculations by rapidly changing cart contents or promo codes.
  • Confirm the frontend shows the backend cooldown message when rate limited.
  • Confirm cart edits remain possible during cooldown and recalculation resumes after the cooldown expires.

Backend Tests

npm run test:api

This runs a lightweight Node test suite covering the promo engine, calculate endpoint validation and rate limiting, and promo stats usage tracking.

Manual API Test Cases

Start the backend:

npx nx serve api

Health check:

curl -i http://localhost:3000/api/health

Get promo usage stats:

curl -i http://localhost:3000/api/promo/stats

Expected checks:

  • Response status is 200.
  • Response contains success: true.
  • Response lists each promo code with uses, max_uses, remaining_uses, and available.

Valid cart with stacked promos:

curl -i -X POST http://localhost:3000/api/cart/calculate \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "usr_123",
    "items": [
      { "sku": "PROD-001", "quantity": 2, "price": "49.99" },
      { "sku": "PROD-002", "quantity": 1, "price": "149.99" }
    ],
    "promo_codes": ["SAVE20", "FLAT10"]
  }'

Expected checks:

  • Request prices are sent as fixed two-decimal strings.
  • subtotal, total_discount, final_total, and each discount amount are returned as fixed two-decimal strings.
  • SAVE20 applies before FLAT10.
  • The API server logs a line with masked user_id, applied promo codes, total discount, and processing time.

Conflict case:

curl -i -X POST http://localhost:3000/api/cart/calculate \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "usr_conflict",
    "items": [
      { "sku": "PROD-001", "quantity": 1, "price": "200.00" }
    ],
    "promo_codes": ["SAVE20", "FLASH30", "VIP15"]
  }'

Expected checks:

  • One of the conflicting priority-1 codes is rejected with CONFLICT_WITH_<CODE>.
  • The accepted priority-1 code is applied before VIP15.

Unknown promo code:

curl -i -X POST http://localhost:3000/api/cart/calculate \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "usr_unknown",
    "items": [
      { "sku": "PROD-001", "quantity": 1, "price": "100.00" }
    ],
    "promo_codes": ["NOTREAL", "FLAT10"]
  }'

Expected checks:

  • NOTREAL appears in rejected_codes with CODE_NOT_FOUND.
  • FLAT10 still applies.

Duplicate and normalized promo codes:

curl -i -X POST http://localhost:3000/api/cart/calculate \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "usr_dupe",
    "items": [
      { "sku": "PROD-001", "quantity": 1, "price": "100.00" }
    ],
    "promo_codes": ["SAVE20", "save20", " FLAT10 "]
  }'

Expected checks:

  • SAVE20 is only applied once.
  • Lowercase and padded promo codes are normalized successfully.

Invalid payload:

curl -i -X POST http://localhost:3000/api/cart/calculate \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": "",
    "items": [
      { "sku": "PROD-001", "quantity": 0, "price": "-10.00" }
    ],
    "promo_codes": ["SAVE20"]
  }'

Expected checks:

  • Response status is 400.
  • Response contains INVALID_CART_REQUEST.

Rate limit test:

for i in $(seq 1 11); do
  echo "Request $i"
  curl -s -i -X POST http://localhost:3000/api/cart/calculate \
    -H 'Content-Type: application/json' \
    -d '{
      "user_id": "rate_limit_user",
      "items": [
        { "sku": "PROD-001", "quantity": 1, "price": "10.00" }
      ],
      "promo_codes": []
    }'
  echo
done

Expected checks:

  • Requests 1 through 10 succeed.
  • Request 11 returns 429.
  • The 429 body includes RATE_LIMITED and retry_after.

About

Implements a promotion code engine with stackable discounts, rate limiting, and real-time usage tracking

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages