Background
The Celebrations team at a large online retailer runs the gift registry service: a couple (or anyone planning an event) creates a registry, adds the products they'd like, and shares it; guests buy from it and the owner sees who bought what. The service is a Node.js REST API (Express + MongoDB via Mongoose). A recent refactor shipped with several defects.
How the system works
Callers
- Every request except
GET /registries/:idneeds anx-user-idheader (the caller's id, already authenticated upstream). A missing or blank header is401with{ "error": "sign in required" }. - The registry's owner is the user who created it.
Errors
Every error response is { "error": "<message>" }:
400invalid input, or an:idthat isn't a valid id401nox-user-id403the caller isn't allowed (see each endpoint)404the registry, or the item, doesn't exist409the request breaks a rule (duplicate item, closed registry, not enough units left)
Dates
- Event dates are calendar dates written
YYYY-MM-DD(a real date:2026-02-29is invalid). - "Today" is the UTC date of the service clock.
The registry object
Every endpoint that returns a registry returns:
{
"id": "665f…",
"ownerId": "owner-ana",
"title": "Ana and Sam's wedding",
"eventDate": "2026-06-10",
"status": "OPEN",
"acceptingPurchases": true,
"items": [
{ "sku": "MUG-01", "name": "Stoneware mug", "priceCents": 1850, "wanted": 2, "purchased": 1, "remaining": 1 }
]
}
statusisOPENorCLOSED. Items are listed in the order they were added.remaining=wanted−purchased.acceptingPurchasesis true when the registry isOPENand today is on or before the 7th day after the event date, inclusive. Event on June 10: purchases are accepted through June 17 and refused from June 18.
Endpoints
POST /registries (signed in): body { "title", "eventDate" }.
titleis trimmed and must be 1–80 characters.eventDatemust be a valid date, today or later.- Returns
201with the new registry (OPEN, no items).
GET /registries/:id (no sign-in needed): 200 with the registry, 404 if there is none.
POST /registries/:id/items (owner only, else 403): body { "sku", "name", "priceCents", "wanted" }.
skuis trimmed and upper-cased, then must be 3–20 characters ofA–Z,0–9or-.nameis trimmed and must be 1–60 characters.priceCentsmust be a whole number ≥ 1.wantedis optional: when it is missing (absent ornull) it is1. Otherwise it must be a whole number from 1 to 20;0, negatives, fractions and strings are400.- A SKU that is already on the registry is
409. ACLOSEDregistry can't get new items (409). - Returns
201with the updated registry.
POST /registries/:id/close (owner only, else 403): sets status to CLOSED and returns
200 with the registry. Closing a closed registry is fine (same response).
POST /registries/:id/purchases (signed in): body { "sku", "qty", "giftMessage" }.
- Checks, in this order:
- the registry exists (
404) - the input is valid (
400):qtyis optional and is1when missing (absent ornull), otherwise a whole number ≥ 1 (0, negatives and fractions are invalid);giftMessageis optional text of at most 200 characters - the caller isn't the owner (
403) - the registry is accepting purchases (
409) - the item exists;
skuis matched trimmed and upper-cased (404) qtyis at most the item'sremaining(409with"only N remaining", for example"only 1 remaining")
- the registry exists (
- Returns
201with{ "purchase": { "id", "registryId", "sku", "qty", "buyerId", "giftMessage", "createdAt" }, "remaining": <units left> }, wherecreatedAtis the service clock as an ISO timestamp.
GET /registries/:id/purchases (owner only, else 403): 200 with { "purchases": [...] },
newest first.
GET /registries/:id/summary (owner only, else 403): 200 with
{ "itemCount", "unitsWanted", "unitsPurchased", "fullyPurchased", "spentCents" }:
itemCount: items on the registry;unitsWanted/unitsPurchased: the sums ofwanted/purchasedfullyPurchased: items with nothing remainingspentCents: the sum ofpriceCents × purchasedover all items
Project structure
src/
app.js createApp(): JSON parsing, routes, error handler
server.js connects to MongoDB and starts the server
routes/registries.js URL → middleware → controller
controllers/
registryController.js create, get, add item, close, summary
purchaseController.js buy an item, list purchases
middleware/
requireUser.js x-user-id → req.userId, else 401
errorHandler.js errors → status + { error }
models/
Registry.js registry with embedded items
Purchase.js one purchase
services/
registryService.js lookups, ownership, the purchase window, items, closing
purchaseService.js purchase rules and listing
summaryService.js the owner's totals
utils/
AppError.js an error with an HTTP status
dates.js YYYY-MM-DD validation and date math
input.js reading counts and text from request bodies
present.js documents → API JSON
tests/
helpers.test.js dates and input helpers
registries.test.js registry endpoints
purchases.test.js purchase endpoints
setup/ in-memory MongoDB for the tests
Your task
Guests and the QA team have reported problems since the refactor:
- The service no longer starts.
- A couple says guests couldn't buy anything a week after their wedding, although the registry should still be open.
- Adding or buying zero units of an item goes through as if one unit were asked for.
Find and fix the defects so that all tests pass.
- Do not modify
tests/,package.jsonorjest.config.js. - Keep your changes minimal. Fix the defects; don't rewrite modules.
- Additional hidden tests check the behaviour described in this README.