Debug and Fix Failing Unit Tests in Java
Company: Bilt Rewards
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
You receive a small Java project for a credit-card rewards service with three failing unit tests: calculatesTriplePointsForRestaurants, returnsUnknownWhenMerchantCodeMissing, and aggregatesDailyPointsCorrectly. Without adding new tests, make minimal, well-justified code changes in up to three files so that all tests pass. Walk through your debugging process: reproducing failures, reading assertions and stack traces, isolating defects, and verifying no regressions. Then implement fixes such as correcting a category check for 'Restaurant', handling null/empty merchantCode safely, and resolving an off-by-one error in daily aggregation. Finally, explain how you would prevent future regressions (e.g., edge-case tests, input validation, and code review checklist).
Quick Answer: This question evaluates proficiency in debugging and maintaining a Java codebase with failing unit tests, testing competencies in unit testing, fault isolation, reading test assertions and stack traces, and verifying regression-free fixes.
You are given a list of credit-card transactions. Implement process_rewards(transactions) that returns a pair: (normalized_merchant_codes, daily_points). For each transaction, normalize merchantCode by replacing any null, empty, or whitespace-only value with "UNKNOWN" (trimming other codes). Compute daily_points by aggregating reward points per UTC calendar date derived from the transaction timestamp. Points are amount * 3 if category equals "Restaurant" (case-insensitive, after trimming), otherwise amount * 1. The timestamp is an ISO 8601 string with timezone (e.g., Z or offsets), and aggregation must be done by UTC day (YYYY-MM-DD). Preserve the order of normalized merchant codes to match the input transaction order.
Constraints
- 0 <= len(transactions) <= 100000
- Each transaction is a dict with keys: amount (int, 0 <= amount <= 10^6), category (string), merchantCode (string or null), timestamp (ISO 8601 string with timezone, e.g., 'Z' or '+02:00')
- Compare category to 'Restaurant' case-insensitively after trimming whitespace
- Normalize merchantCode: if null, empty, or whitespace-only, use 'UNKNOWN'; otherwise trim surrounding whitespace
- Aggregate points by UTC date (YYYY-MM-DD) derived from timestamp
- Return value: (normalized_merchant_codes: list[str], daily_points: dict[str, int])
Solution
from collections import defaultdict
from datetime import datetime, timezone
def process_rewards(transactions: list[dict]) -> tuple[list[str], dict[str, int]]:
normalized_codes: list[str] = []
daily_points: defaultdict[str, int] = defaultdict(int)
for t in transactions:
# Normalize merchantCode
code = t.get("merchantCode", None)
code_str = "" if code is None else str(code)
code_trimmed = code_str.strip()
normalized_codes.append(code_trimmed if code_trimmed != "" else "UNKNOWN")
# Compute multiplier based on category (case-insensitive, trimmed)
category = t.get("category", "")
cat_str = str(category).strip().lower()
multiplier = 3 if cat_str == "restaurant" else 1
# Amount (assume integer per constraints)
amount = t.get("amount", 0)
try:
amt_int = int(amount)
except Exception:
amt_int = 0
# UTC date extraction from timestamp
ts = t.get("timestamp")
if not isinstance(ts, str):
raise ValueError("timestamp must be an ISO 8601 string with timezone")
iso = ts.replace("Z", "+00:00")
dt = datetime.fromisoformat(iso)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
utc_date = dt.astimezone(timezone.utc).date().isoformat()
# Aggregate points
daily_points[utc_date] += amt_int * multiplier
return normalized_codes, dict(daily_points)
Explanation
Iterate once over the transactions. Normalize merchant codes by trimming whitespace and replacing null/empty values with 'UNKNOWN'. Determine the points multiplier by comparing the trimmed, lowercased category to 'restaurant'. Parse each ISO 8601 timestamp and convert it to UTC before taking the calendar date to prevent off-by-one day errors from timezones. Use a dictionary to accumulate points per date. Return the normalized merchant codes (in input order) and the per-day totals.
Time complexity: O(n). Space complexity: O(n).
Hints
- Parse ISO 8601 timestamps and convert to UTC before taking the date.
- Consider replacing a trailing 'Z' with '+00:00' for Python's fromisoformat.
- Use a dictionary or defaultdict(int) to accumulate points by date.
- Trim strings before checking emptiness or comparing to 'Restaurant'.