Ghost Rows in the Audit Log — JavaScript Bug Hunt

The audit-log API of an internal admin tool paginates records for the dashboard.

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Medium
  • Modelled on: Every admin dashboard
  • Visible tests: page 1 starts at the first record; page 2 continues without overlap; final partial page disables hasNext
  • Reward: 50 XP for a complete fix

Briefing

The audit-log API of an internal admin tool paginates records for the dashboard. Customers report that page 1 is missing the newest records, and the same rows appear on two different pages.

The Express handler and the mock data layer are locked — the bug is isolated in paginate.js, the pure helper the handler delegates to.

Make the whole suite green without touching the locked files.

Bug report

BUG-3341 · Priority: High · Reported by: platform-eng

The dashboard shows 10 rows per page. With 25 records in the table:

  • Page 1 starts at record #11 instead of record #1
  • Records #21–25 appear on BOTH page 2 and page 3
  • The "next page" arrow stays enabled on the final page, leading to an empty page 4

API contract (see handler.js): page is 1-based; response = { items, page, hasNext }.

Logs

GET /api/audit?page=1&limit=10 -> items[0].id = 11 (expected 1)
GET /api/audit?page=3&limit=10 -> hasNext = true (expected false)

The code as shipped

src/api/paginate.js (editable)

// Pure pagination helper used by the audit-log handler.
// page is 1-based. Returns { skip, take, hasNext }.
exports.getPageWindow = function (page, limit, totalCount) {
  var skip = page * limit;
  var take = limit;
  var hasNext = skip + take <= totalCount;
  return { skip: skip, take: take, hasNext: hasNext };
};

Read-only context: src/api/db.js, src/api/handler.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.