Repo X-ray
data/sample-serviceTwo 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.
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
There is no layering. One file holds the data fixture, the arithmetic, and both routes.
| Path | Owns | Lines |
|---|---|---|
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 |
The README names neither route. There is no list route and no health route, whatever you might expect from a service like this.
| Route | In | Out | Errors |
|---|---|---|---|
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 |
Totals recomputed after the fix and checked against the line items by hand.
| Order | Account | Lines | Total before | Total after |
|---|---|---|---|---|
A-1001 | Account A | 2 × GIS-STD @ 1200.00 | 1202.00 | 2400.00 |
A-1002 | Account A | 10 × MA-PRO @ 45.00, 1 × GIS-STD @ 1200.00 | 1256.00 | 1650.00 |
B-2001 | Account B | none | 0.00 | 0.00 |
| Account A rollup | 2458.00 | 4050.00 | ||
lines list is empty, so the loop body never ran.
That is exactly why one of the five tests was blind to the bug.
Run from data/sample-service/ with python -m pytest -q. Left column is the state before the fix.
| Test | Before | What 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
(10 + 45) + (1 + 1200), which names the bug outright.
$ python -m pytest -q ..... [100%] 5 passed in 0.63s
test_get_order_ok so the money path is no longer unguarded.Click a risk for the evidence. Ranked by how quietly it does damage, not by how hard it is to fix.
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.
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.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.
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
test_account_total has no assertion on its total. It should read 4050.00./accounts/{account}/total.round would matter.
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 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
1 failed, 4 passed becomes 5 passed, and the endpoint test now
fails if the helper regresses.