Bugnik
← All problems

47Delivery Address Changes API

Java · Spring BootEasyBug fixSuggested time: 60 min

Delivery address change REST API (same-zone PIN codes, change limit and fee, delivery instructions)

New to repository-based OA questions? Read the guide.

Open in IDE

Before you start

You'll get more out of this problem if you're comfortable with:

Language

  • Java 17 records, enums and exceptions
  • Collections and Optional

Libraries and frameworks

  • Spring Boot basics: @RestController, @Service and constructor injection
  • Request headers and JSON bodies in Spring MVC

Build and test tools

  • Reading javac errors and JUnit 5 / MockMvc test failures

Reasoning

  • Delivery operations: order statuses and addresses

Background

The Last Mile team at a large online retailer lets customers change where an order is delivered while it is still at the delivery station, and leave instructions for the driver. A Spring Boot service owns these rules; the dispatch system also uses it to move orders along. Since the latest release, the address-change flow has had problems in testing.

How the system works

Callers

  • Customers send X-Customer-Id. A customer may only see or change their own orders.
  • The dispatch system sends X-Role: dispatcher (compared case-insensitively, after trimming).
  • A missing X-Customer-Id on a customer endpoint is 401 with error SIGN_IN_REQUIRED.

Errors

Every error response is { "error": "<CODE>", "message": "<detail>" }.

StatusCodes
400INVALID_ADDRESS, INVALID_ORDER, TOO_LONG, STATUS_REQUIRED, BAD_REQUEST (unreadable body)
401SIGN_IN_REQUIRED
403NOT_YOUR_ORDER, DISPATCHERS_ONLY
404ORDER_NOT_FOUND
409ORDER_EXISTS, TOO_LATE, CHANGE_LIMIT, OUTSIDE_ZONE, SAME_ADDRESS, BAD_TRANSITION

Addresses

Addresses travel as one line of text: line1 | line2 | city | PIN.

  • Exactly four parts separated by |; each part is trimmed.
  • line1 and city are required; line2 may be empty. Each part is at most 80 characters.
  • PIN is the 6-digit postal code and doesn't start with 0. Its first 3 digits are the delivery zone (560034 is zone 560).
  • Anything else is 400 INVALID_ADDRESS.
  • In JSON responses an address is { "line1", "line2", "city", "pin" }.

Order statuses

PLACED → PACKED → OUT_FOR_DELIVERY → DELIVERED, plus CANCELLED. The dispatch system moves an order one step forward at a time, or cancels it while it is PLACED or PACKED; any other move (including anything out of CANCELLED) is 409 BAD_TRANSITION.

Endpoints

POST /api/orders (dispatch only, else 403 DISPATCHERS_ONLY): body { "id", "customerId", "address" } (address in the one-line format). id and customerId are trimmed and required (400 INVALID_ORDER); an existing id is 409 ORDER_EXISTS. The order starts PLACED. Returns 201 with the order.

An order in JSON: { "id", "status", "address", "deliveryNote", "safePlace", "changesUsed", "changeFeesCents" } (deliveryNote and safePlace start as "").

GET /api/orders/{id}: the customer's order (404 ORDER_NOT_FOUND, or 403 NOT_YOUR_ORDER for someone else's).

POST /api/orders/{id}/address-changes: body { "address" }. Checked in this order:

  1. The order exists (404) and is the caller's (403).
  2. The order is PLACED or PACKED; later it is 409 TOO_LATE.
  3. At most 2 changes per order; a third is 409 CHANGE_LIMIT.
  4. The address is valid (400 INVALID_ADDRESS).
  5. The new PIN is in the same delivery zone as the current address (409 OUTSIDE_ZONE).
  6. The new address differs from the current one (409 SAME_ADDRESS).

The first change is free; the second costs 4900 cents (₹49), added to the order's changeFeesCents. Returns 201 with { "orderId", "changeNumber", "address", "feeCents", "totalChangeFeesCents" }.

PATCH /api/orders/{id}/instructions: the customer's driver instructions. Body fields deliveryNote (at most 200 characters) and safePlace (at most 60) are trimmed; a field that is left out keeps its value, and a longer value is 400 TOO_LONG. Only those two fields are used. Allowed until the order is DELIVERED or CANCELLED (409 TOO_LATE). Returns 200 with the order.

PATCH /api/orders/{id}/status (dispatch only): body { "status" }. A missing status is 400 STATUS_REQUIRED; the move must follow the status rules above. Returns 200 with the order.

Project structure

src/main/java/com/bugnik/addresses/
  AddressesApplication.java          Spring Boot entry point
  model/
    Address.java                     an address and its delivery zone
    AddressChange.java               the result of an address change
    Order.java                       an order on its way to the customer
    OrderStatus.java                 statuses and the allowed moves
    OrderUpdate.java                 PATCH request body
  repository/
    OrderRepository.java             in-memory orders
  service/
    AddressChangeService.java        address changes, instructions, status moves
    DeliveryException.java           an error with a status and a code
  util/
    AddressFormatException.java      the address text is malformed
    AddressParser.java               the one-line address format
  web/
    OrderController.java             the endpoints and their JSON shapes
    ApiError.java                    the error body
    ApiExceptionHandler.java         errors → status + body
src/test/java/com/bugnik/addresses/
  util/AddressParserTest.java        address format
  service/AddressChangeServiceTest.java   service rules
  service/Fixtures.java              shared test data
  web/OrderControllerTest.java       endpoints (MockMvc)

Your task

The QA team reports:

  • Customers are told their new address is outside their delivery zone when it is just down the road.
  • Some customers say their delivery label looks wrong.
  • Some orders' tracking pages jumped ahead of where the parcel really was.

Find and fix the defects so that all tests pass.

  • Do not modify src/test/ or pom.xml.
  • Keep your changes minimal. Fix the defects; don't rewrite modules.
  • Additional hidden tests check the behaviour described in this README.