Bugnik
← All problems

24Review Helpfulness Votes API

Java · Spring BootEasyFeatureSuggested time: 60 min

Review helpfulness votes REST API (implement rank; votes, burying, tie-breaks)

Sign in to start

Before you start

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

Language

  • Interfaces and @Override
  • Streams (filter, map, sorted) and chained Comparators (reversed, thenComparing)
  • Operator precedence in arithmetic expressions

Libraries and frameworks

  • Spring Boot REST controllers and services

Build and test tools

  • Reading javac "does not override" errors in the Maven output
  • Reading JUnit 5 / MockMvc test failures

Reasoning

  • Ranking with a score and several tie-breakers

Background

The Customer Reviews team at a large online retailer lets shoppers mark reviews as helpful or not helpful, and wants the most useful reviews at the top of each product page. Posting and voting already work (mostly); the ranking that orders a product's reviews was left unfinished when its author moved teams.

How the system works

Reviews and votes

  • A review belongs to a product and has an author, 1–5 stars, text, a posting date and a verified flag (the author bought the product here). Ids are R-1, R-2, ... in posting order. Stars outside 1–5 are 400 INVALID_STARS.
  • A shopper has at most one vote per review: voting again on the same review replaces their earlier vote (so they can change their mind). Votes on different reviews are independent.
  • Authors can't vote on their own review (403 OWN_REVIEW). Voting on an unknown review is 404 NOT_FOUND.
  • Removing a vote that doesn't exist is 404 NO_VOTE.
  • A review's tally is its number of helpful and not-helpful votes.

Ranking

RankingService.rank(productId) returns the product's reviews for display:

  1. Score = 2 × helpful − notHelpful, plus 3 for a verified review. (RankingService.score computes this.)
  2. Buried reviews are left out: those with at least 5 not-helpful votes and more than twice as many not-helpful as helpful votes (RankingService.isBuried).
  3. The rest are sorted:
    1. higher score first;
    2. then more total votes (helpful + not helpful) first;
    3. then the newer posting date first;
    4. then the smaller id (plain string order, so R-10 comes before R-9).
  4. Each entry carries the review, its helpful and not-helpful counts, and its score. A product without reviews gives an empty list.

Example: R-1 with 3 helpful votes (score 6), verified R-2 with 1 helpful vote (score 5) and R-3 with none (score 0) rank R-1, R-2, R-3.

API

Method and pathBodySuccess
POST /api/products/{productId}/reviews{stars, text, verified, postedOn}201 review
GET /api/products/{productId}/reviews200 ranked reviews: [{review, helpful, notHelpful, score}]
POST /api/reviews/{id}/votes{helpful: true/false}200 tally {helpful, notHelpful}
DELETE /api/reviews/{id}/votes204

Posting and voting need an X-Customer-Id header (the author or the voter). A missing header, missing helpful, blank text, missing postedOn or malformed JSON is 400 INVALID_REQUEST. Errors return {"error": "<CODE>", "message": "..."}.

Project structure

src/main/java/com/bugnik/reviewvotes/
  ReviewVotesApplication.java     Spring Boot entry point
  model/                          Review, Vote, VoteTally, RankedReview
  repository/                     in-memory ReviewRepository and VoteRepository
  service/ReviewService.java      posting, voting, removing votes, tallies
  service/ReviewRanking.java      the ranking interface the controller uses
  service/RankingService.java     score, isBuried and rank (the ranking)
  service/ReviewException.java    business errors (status + code)
  web/                            ReviewController, ApiError, ApiExceptionHandler
src/test/java/com/bugnik/reviewvotes/   unit and MockMvc tests

Your task

Implement RankingService.rank as specified in "Ranking", and fix any defects that stop the test suite from passing.

  • 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.