Katkat

Debug a Layered Book Search Service Against Five Search Rules

AmazonSoftware Engineer · Online Assessment

Last updated

Given a layered bookstore search backend with planted bugs, write failing tests for substring matching, scope selection, case-insensitivity, multi-key ordering and visibility, then locate and fix every defect without changing the API contract. Tests test-first debugging across layers and query design.

An online bookstore's search endpoint returns wrong results. The code is split into layers: an entity, a repository, a service, and a controller. Users reported several problems, and nobody is sure how many bugs there are. Your job is to make the endpoint satisfy these five rules without changing its URL, its query parameters, or its response JSON shape.

  1. Substring match: a book matches if the query text appears anywhere inside the searched field.
  2. Scope: in=title searches titles, in=authors searches author names, and in=all (the default when in is missing) matches a book if its title or any of its authors matches. Each book appears at most once.
  3. Case-insensitive: NIGHT, night, and Night all behave the same.
  4. Ordering: rating descending, then review count descending, then title A to Z ignoring case, then id ascending.
  5. Visibility: a book with listed = False is never returned.

Also: a query that is empty or only whitespace returns {"results": []}.

Current code

from dataclasses import dataclass, field


@dataclass
class Book:
    id: int
    title: str
    authors: list = field(default_factory=list)
    rating: float = 0.0        # 0.0 - 5.0
    reviews: int = 0
    listed: bool = True


class BookRepository:
    def __init__(self, books):
        self._books = list(books)

    def find_by_title(self, text):
        return [b for b in self._books if b.title == text]

    def find_by_author(self, text):
        return [b for b in self._books if text in b.authors]

    def find_all(self):
        return list(self._books)


class SearchService:
    def __init__(self, repo):
        self.repo = repo

    def search(self, text, scope):
        text = text.lower()
        if scope == "title":
            hits = self.repo.find_by_title(text)
        elif scope == "author":
            hits = self.repo.find_by_author(text)
        else:
            hits = self.repo.find_by_title(text) + self.repo.find_by_author(text)
        hits.sort(key=lambda b: b.rating)
        return hits


class SearchController:
    SCOPES = {"title": "title", "author": "author", "all": "all"}

    def __init__(self, service):
        self.service = service

    def get(self, params):
        """GET /api/books/search?q=...&in=title|authors|all"""
        scope = self.SCOPES.get(params.get("in", "all"), "title")
        books = self.service.search(params.get("q", ""), scope)
        return {"results": [{"id": b.id, "title": b.title} for b in books]}

Part 1: tests first

Before you touch the code, write unit tests that pin down each rule and fail against the current code. Design the fixture data so each test fails for one clear reason. For example, the ordering test needs books with tied ratings, and the visibility test needs an unlisted book that would otherwise match.

Part 2: find and fix

List every defect you find, which layer it lives in, and which rule it breaks. Then fix them so that all of your tests pass. Keep the controller's public contract the same.

Part 3: production version

In production the repository is backed by a relational database with millions of books, and authors is a separate table. Explain how rules 1 to 5 translate into a query, what indexes make substring search fast enough, and how you would add pagination without breaking the ordering guarantees.

Hints

Hint 1

Write the fixture so each test can fail for exactly one reason. Ties in rating and an unlisted book that would otherwise match are essential.

Hint 2

Trace one request from the controller down. Check which scope string actually reaches the service when the client sends in=authors.

Hint 3

Look at what concatenating two partial result lists does to a book that matches both its title and an author.

Hint 4

Once you fix substring matching, what does an empty query match?

Follow-up questions

  • How would you add accent-insensitive matching (e.g. 'Garcia' matching 'García')?
  • Product wants matches in the title ranked above matches only in an author. How do you change the ordering?
  • How would you prevent this class of bug from returning, beyond unit tests (contract tests, property-based tests)?