Repo X-ray

data/sample-service

Two read-only endpoints, 34 lines, and both of them computed money through one helper that added quantity to unit price instead of multiplying. Four of five tests passed over it. The fix is one character; the reason it survived is the interesting part.

Health at a glance

Every number below came from running the suite in this repo, not from the README.

Tests before fix

4 / 5

1 failed in 3.07s

Tests after fix

5 / 5

0 failed in 0.63s

Assertion gaps

3

tests that never read total

Untested branch

1

the 404 on /accounts

Lines changed

2

1 fix, 1 new assertion

Modules and what each owns

There is no layering. One file holds the data fixture, the arithmetic, and both routes.

PathOwnsLines
app/main.py Everything: the ORDERS fixture, order_total(), and both routes. The single shared helper is why one bug reached both endpoints. 34
app/__init__.py Empty. Package marker only. 0
tests/test_orders.py All five tests. Mixes HTTP tests through TestClient with direct unit calls into order_total. 29
pytest.ini pythonpath = ., so pytest only resolves app.main when run from data/sample-service/. 3

Endpoints read from the code, not the README

The README names neither route. There is no list route and no health route, whatever you might expect from a service like this.

RouteInOutErrors
GET /orders/{order_id} main.py:21 order_id path string, matched exactly against ORDERS keys The order object spread flat, plus a computed total 404 order not found
GET /accounts/{account}/total main.py:29 account path string, compared with == against each order's account field. Account names carry a space, so the real path is /accounts/Account%20A/total. {account, orders: <count>, total: <sum>} 404 account not found when zero orders match

The store 3 orders, 2 accounts, corrected totals

Totals recomputed after the fix and checked against the line items by hand.

OrderAccountLinesTotal beforeTotal after
A-1001Account A 2 × GIS-STD @ 1200.00 1202.00 2400.00
A-1002Account A 10 × MA-PRO @ 45.00, 1 × GIS-STD @ 1200.00 1256.00 1650.00
B-2001Account B none 0.00 0.00
Account A rollup 2458.00 4050.00
B-2001 is the only order the bug could not touch. Its lines list is empty, so the loop body never ran. That is exactly why one of the five tests was blind to the bug.

Test summary 5 tests

Run from data/sample-service/ with python -m pytest -q. Left column is the state before the fix.

TestBeforeWhat it actually asserts
test_total_two_linestest_orders.py:18 fail The full sum for a two-line order. The only test that read a total it could check.
test_get_order_oktest_orders.py:7 pass gap Status 200 and id. Called the endpoint, got a wrong total in the body, never looked at it.
test_account_totaltest_orders.py:26 pass gap Status 200 and orders == 2. The endpoint's whole purpose is the total field, and nothing asserts on it.
test_empty_order_total_is_zerotest_orders.py:22 pass gap Zero for B-2001. Passes whether the helper adds or multiplies, because it never enters the loop.
test_get_order_missingtest_orders.py:14 pass The 404 path. Genuine coverage.
$ python -m pytest -q
..F..                                                    [100%]
=================================== FAILURES ===================================
____________________________ test_total_two_lines _____________________________

    def test_total_two_lines():
>       assert order_total(ORDERS["A-1002"]) == 10 * 45.0 + 1 * 1200.0
E       AssertionError: assert 1256.0 == ((10 * 45.0) + (1 * 1200.0))

tests\test_orders.py:18: AssertionError
1 failed, 4 passed in 3.07s
Reading it: expected 1650.0, got 1256.0. Not a rounding drift and not off by a constant. 1256 is (10 + 45) + (1 + 1200), which names the bug outright.
$ python -m pytest -q
.....                                                    [100%]
5 passed in 0.63s
After the fix, plus one new assertion in test_get_order_ok so the money path is no longer unguarded.

Risks ranked, worst first

Click a risk for the evidence. Ranked by how quietly it does damage, not by how hard it is to fix.

3 findings
1 order_total adds quantity to unit price instead of multiplying app/main.py:17 · fixed evidence +evidence −
def order_total(order: dict) -> float:
    """Sum of qty * unit across lines. Empty orders total 0."""
    total = 0.0
    for line in order["lines"]:
        total += line["qty"] + line["unit"]   # the docstring one line up says *
    return round(total, 2)

Every order with a non-empty line was wrong, on both endpoints, and it failed quietly. A-1001 reported 1202.00 against a true 2400.00. The response was a well-formed float of plausible magnitude, so nothing downstream could tell it apart from a correct answer. No exception, no log line, no 500. Money that is wrong by 49% and looks right is worse than money that crashes.

The docstring on the line directly above states the correct rule. The code and its own documentation disagreed, and the documentation was right.

Blast radius

  • GET /orders/{order_id}, the total field, for A-1001 and A-1002.
  • GET /accounts/{account}/total, the total field, which sums the same broken helper. Account A read 2458.00 against a true 4050.00.
  • Not B-2001, and not either 404 path. That is the whole of the untouched surface.
2 The tests assert around the money path, which is why the bug survived tests/test_orders.py:7, :22, :26 · one gap closed evidence +evidence −

Four of five tests passed over arithmetic that was wrong on every non-empty order. Each one missed it for a different reason, and none of the reasons is an accident of luck:

  • test_get_order_ok called the endpoint and asserted on id alone. The wrong total was sitting in the response body it had already parsed.
  • test_account_total asserted orders == 2. The route exists to return a sum, and the sum went unchecked.
  • test_empty_order_total_is_zero picked B-2001, the one order whose lines is empty, so the loop body never executed.

Line coverage on this repo looks close to complete. Assertion coverage is not. A green suite here proved the routes returned 200 and the right shape. It read as though it proved the numbers were right. That gap is more dangerous than the bug itself, because it is the mechanism that would let the next such bug through untouched.

Closed in this change

 def test_get_order_ok():
     r = client.get("/orders/A-1001")
     assert r.status_code == 200
     assert r.json()["id"] == "A-1001"
+    assert r.json()["total"] == 2 * 1200.0

Still open

  • test_account_total has no assertion on its total. It should read 4050.00.
  • No test drives the 404 branch of /accounts/{account}/total.
  • No test covers an order with a line quantity of zero, or a fractional unit price where round would matter.
3 Both endpoints trust their input's shape, and one 404 states something untrue app/main.py:16, :31 · open evidence +evidence −

order_total indexes order["lines"], line["qty"], and line["unit"] with no guards. An order missing a key raises KeyError; a string quantity raises TypeError. Either way the caller gets a 500 and a stack trace where a 4xx or a clean 500 body belongs. Today ORDERS is a hardcoded fixture so every record is well formed. The first time this store is loaded from anywhere else, that stops being true.

Separately, account_total matches the account name with == on a raw path segment. /accounts/account%20a/total returns 404 account not found for an account that plainly exists. The error message asserts a fact that is false. A caller debugging against that message looks in the wrong place.

Left unfixed on purpose. Both changes alter response contracts, which is a decision for whoever owns the API, not a drive-by edit inside a repo review.

The fix applied and verified

The suite going green is not evidence the service is correct. It is evidence that the one test which happened to check a total now agrees with the code. The multiply ships together with a new assertion on total for exactly that reason.

--- a/app/main.py
+++ b/app/main.py
@@ -13,7 +13,7 @@ def order_total(order: dict) -> float:
     """Sum of qty * unit across lines. Empty orders total 0."""
     total = 0.0
     for line in order["lines"]:
-        total += line["qty"] + line["unit"]
+        total += line["qty"] * line["unit"]
     return round(total, 2)

--- a/tests/test_orders.py
+++ b/tests/test_orders.py
@@ -7,6 +7,7 @@ def test_get_order_ok():
     r = client.get("/orders/A-1001")
     assert r.status_code == 200
     assert r.json()["id"] == "A-1001"
+    assert r.json()["total"] == 2 * 1200.0
Two lines. 1 failed, 4 passed becomes 5 passed, and the endpoint test now fails if the helper regresses.