Background
The Shopper Engagement team at a large online retailer lets customers ask to be notified when a product gets cheaper. Customers set a target price; every price feed from merchandising is checked against the active alerts. Since the last release, customers whose target price is hit exactly never hear about it, and asking to cancel an alert that isn't theirs (or doesn't exist) crashes the service instead of answering "not found".
How the system works
Prices
Each SKU has a current price in cents. SKUs are stored in upper case; any SKU in a request or a feed is trimmed and upper-cased first. A SKU without a known price is an unknown product.
Alerts
-
Create (customer, SKU, target in cents):
- the product must have a known price (
404 UNKNOWN_PRODUCT); - the target must be more than 0 (
400 INVALID_TARGET); - the target must be below the current price (
400 TARGET_NOT_BELOW_PRICE); - the customer may not already have an
ACTIVEalert for that SKU (409 DUPLICATE_ALERT); - a customer may have at most 10
ACTIVEalerts (409 TOO_MANY_ALERTS); triggered and cancelled alerts don't count.
A new alert is
ACTIVE, with idAL-1,AL-2, ... in creation order andtriggeredPriceCents0. - the product must have a known price (
-
List: a customer's own alerts (any status), sorted by SKU, then creation order.
-
Cancel: only an
ACTIVEalert can be cancelled (409 NOT_ACTIVEotherwise); it becomesCANCELLED. An unknown id or another customer's alert is404 NOT_FOUND(we don't reveal that it exists).
Price feed
Plain text, one row per line: sku,price.
sku,price
KT-100,39.99
HD-7,120
- An optional header row (the first non-blank line, if it starts with
skuin any case) is skipped; blank lines are ignored; fields are trimmed. - Exactly 2 fields; the SKU must not be empty; the price is dollars (1–7 digits, optionally
.and 1 or 2 digits) and more than 0. - One bad line rejects the whole feed (
400 INVALID_FEED) and nothing is applied. - Rows are applied in order (a SKU may appear more than once; each row sets the price). New SKUs are added.
- After each row, every
ACTIVEalert for that SKU whose target is at or above the new price (the price is at or below the target) becomesTRIGGERED, recording that price. An alert triggers only once. - The result says how many rows were applied and which alerts triggered, in order.
API
| Method and path | Body | Success |
|---|---|---|
POST /api/alerts | {sku, targetCents} | 201 alert |
GET /api/alerts | 200 my alerts | |
DELETE /api/alerts/{id} | 200 the cancelled alert | |
POST /api/prices/feed | the feed, Content-Type: text/plain | 200 {updated, triggered: [ids]} |
GET /api/prices/{sku} | 200 {sku, priceCents}; 404 UNKNOWN_PRODUCT |
The alert endpoints need an X-Customer-Id header. An alert is {id, customerId, sku, targetCents, status, triggeredPriceCents}.
Errors
Errors return {"error": "<CODE>", "message": "..."} with the statuses above. A missing header, a blank sku or malformed JSON is 400 INVALID_REQUEST.
Project structure
src/main/java/com/bugnik/pricealerts/
PriceAlertsApplication.java Spring Boot entry point
model/ Alert, AlertStatus, PriceUpdate
repository/ in-memory AlertRepository and PriceRepository
service/AlertService.java create / list / cancel rules
service/FeedService.java applying a feed and triggering alerts
service/AlertException.java business errors (status + code)
util/PriceFeedParser.java reads the feed text (throws FeedFormatException)
web/ AlertController, ApiError, ApiExceptionHandler
src/test/java/com/bugnik/pricealerts/ unit and MockMvc tests
Your task
Customers and support have reported problems since the last release:
- The project no longer builds.
- A customer with a $39.99 target isn't notified when the price drops to exactly $39.99.
- Cancelling an alert that doesn't exist, or belongs to someone else, returns a server error instead of
404.
Find and fix the defects so that all tests pass.
- Do not modify
src/test/orpom.xml. - Keep your changes minimal. Fix the defects; don't rewrite modules.
- Additional hidden tests check the behaviour described in this README.