Background
The Product Pages team at a large online retailer runs the "Questions and answers" box under every product: shoppers ask about a product, and other shoppers or the seller answer. The service is a Node.js REST API (Express + MongoDB via Mongoose). A recent release went out with a few problems that shoppers and sellers noticed within a day.
How the system works
Callers
- Every request needs an
x-user-idheader (already authenticated upstream). A missing or blank header is401with{ "error": "sign in required" }. - The catalog team also sends
x-role: catalog(roles are compared case-insensitively).
Errors
Every error response is { "error": "<message>" }: 400 invalid input, 401 no x-user-id,
403 not allowed, 404 unknown product or question, 409 a rule was broken.
Products
POST /products (catalog team only, else 403): body { "sku", "title", "sellerId" }.
skuis trimmed and upper-cased and must be 3–20 letters, digits or hyphens. A SKU that already exists is409.titleis trimmed, 1–120 characters;sellerIdis trimmed, 1–40 characters.sellerIdis the user id of the seller who lists the product.- Returns
201with{ "sku", "title", "sellerId" }.
In paths, :sku is matched case-insensitively (/products/bk-2207 is BK-2207); an unknown SKU is 404.
GET /products/:sku: 200 with { "sku", "title", "sellerId", "questions": { "total", "open", "unanswered", "sellerAnswered" } }:
all of the product's questions, the open ones, the open ones with no answer yet, and the ones the
seller has answered.
Questions
A question as the API returns it:
{ "id", "sku", "text", "askedAt", "status", "answerCount", "sellerAnswered" }, where status is
OPEN or CLOSED and askedAt is an ISO timestamp in UTC. Questions are anonymous: no response
includes the user id of the person who asked.
POST /products/:sku/questions: body { "text" }.
textis trimmed and must be 10–300 characters (else400).- The product's seller can't ask about their own product (
403). - A shopper may have at most 3 open questions with no answers yet on the same product. Asking
another one while 3 are waiting is
409; once one of them is answered or closed, the shopper may ask again. - Returns
201with the question (statusOPEN,answerCount0,sellerAnsweredfalse).
GET /products/:sku/questions?answered=…&limit=N: 200 with { "questions": [...] }, newest
askedAt first (questions asked at the same moment: the later-created one first).
answeredis optional:truekeeps only questions with at least one answer,falseonly questions with none; without it, every question is listed. It is read case-insensitively (FALSE,True); any other value is400.limitis optional (default 20) and must be a whole number from 1 to 50 (else400).
GET /questions/:id: 200 with the question plus "answers": [...]: the seller's answers first,
then the oldest answer first. An id that doesn't belong to a question is 404.
PATCH /questions/:id: body { "text" } (same rules as when asking). Only the person who asked may
reword the question (else 403), and only while it has no answers (else 409). Returns 200 with
the question.
POST /questions/:id/close: the person who asked or the product's seller may close a question
(anyone else gets 403). Closing a closed question is 409. Returns 200 with the question
(status CLOSED).
Answers
POST /questions/:id/answers: body { "text" }.
textis trimmed, 2–500 characters (else400).- Anyone except the person who asked may answer (
403). A closed question takes no answers (409). - An answer from the product's seller has
fromSellertrue; it also sets the question'ssellerAnswered. Every answer adds one to the question'sanswerCount. - Returns
201with{ "id", "questionId", "text", "answeredAt", "fromSeller" }(no user id either).
Project structure
src/
app.js createApp(): JSON parsing, routes, error handler
server.js connects to MongoDB and starts the server
routes/
products.js /products
questions.js /questions
controllers/
productController.js add a product, product page counts, ask, list questions
questionController.js question page, edit, close, answer
middleware/
auth.js x-user-id and role checks
errorHandler.js errors → status + { error }
models/
Product.js a product page and its seller
Question.js a shopper's question
Answer.js an answer to a question
services/
productService.js product lookups and question counts
questionService.js asking, listing, editing, closing and answering rules
utils/
AppError.js an error with an HTTP status
input.js reading text, SKUs and query values
present.js documents → API JSON
tests/
input.test.js input helpers
questions.test.js product and question endpoints
answers.test.js answer, close and edit endpoints
setup/ in-memory MongoDB for the tests
Your task
Shoppers and sellers report these problems:
- A shopper says someone found out which questions they asked.
- Filtering a product's questions by whether they've been answered shows the wrong questions.
- Settled questions keep collecting new answers.
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.