fix(tests): the counts query selects four columns, not three (#4154)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / integration (push) Successful in 40s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m36s
CI & Build / Build & push image (push) Successful in 23s

The new fixtures fed (milestone_id, status, count) and the query also
carries max(updated_at), which last_touched_at is computed from. Six
tests in the new module died unpacking it; the product path was never
reached, and the rest of the suite was green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-18 12:11:39 -04:00
co-authored by Claude Opus 5
parent 5c6175ad97
commit 94ecb633a0
+10 -6
View File
@@ -53,6 +53,10 @@ def _sessions(results: list, counter: list[int], seen_sql: list | None = None):
the ORDER of `results` pins the order of the queries — which is what makes the ORDER of `results` pins the order of the queries — which is what makes
the query-count assertion meaningful rather than incidental. the query-count assertion meaningful rather than incidental.
Counts rows are 4-tuples — (milestone_id, status, count, max(updated_at)) —
because the counts query also carries the touched-at that `last_touched_at`
is computed from. A 3-tuple unpacks as a ValueError, not as a wrong answer.
`seen_sql` collects the rendered statements. The ordering these surfaces `seen_sql` collects the rendered statements. The ordering these surfaces
have to agree on lives in an ORDER BY, which no amount of feeding rows to a have to agree on lives in an ORDER BY, which no amount of feeding rows to a
mock can exercise — a stand-in hands back whatever order the test chose. mock can exercise — a stand-in hands back whatever order the test chose.
@@ -116,7 +120,7 @@ async def test_a_milestone_row_names_its_next_open_step():
"""The listing that says "8 of 9" now says WHICH one, in the same read.""" """The listing that says "8 of 9" now says WHICH one, in the same read."""
rows = await _summaries( rows = await _summaries(
[_milestone_row(409)], [_milestone_row(409)],
[(409, "done", 8), (409, "todo", 1)], [(409, "done", 8, None), (409, "todo", 1, None)],
[(409, 4154, "Step 8 — a cited record carries its status", "todo")], [(409, 4154, "Step 8 — a cited record carries its status", "todo")],
) )
assert rows[5][0]["next_step"] == { assert rows[5][0]["next_step"] == {
@@ -134,7 +138,7 @@ async def test_a_finished_plan_names_no_next_step_rather_than_omitting_the_key()
test for its absence to learn the answer, and a reader who forgets is back test for its absence to learn the answer, and a reader who forgets is back
to guessing — which is the failure this step exists for. to guessing — which is the failure this step exists for.
""" """
rows = await _summaries([_milestone_row(416)], [(416, "done", 9)], []) rows = await _summaries([_milestone_row(416)], [(416, "done", 9, None)], [])
assert rows[5][0]["next_step"] is None assert rows[5][0]["next_step"] is None
assert "next_step" in rows[5][0] assert "next_step" in rows[5][0]
@@ -149,7 +153,7 @@ async def test_the_earliest_open_step_wins_not_the_earliest_step():
""" """
rows = await _summaries( rows = await _summaries(
[_milestone_row(409)], [_milestone_row(409)],
[(409, "done", 2), (409, "todo", 2)], [(409, "done", 2, None), (409, "todo", 2, None)],
# The query filters to open steps, so the closed ones never appear — # The query filters to open steps, so the closed ones never appear —
# this asserts the ORDER of what does: earliest open, not last written. # this asserts the ORDER of what does: earliest open, not last written.
[(409, 4015, "Step 6", "in_progress"), (409, 4154, "Step 8", "todo")], [(409, 4015, "Step 6", "in_progress"), (409, 4154, "Step 8", "todo")],
@@ -169,7 +173,7 @@ async def test_the_batch_does_not_fan_out_per_milestone():
counter = [0] counter = [0]
await _summaries( await _summaries(
[_milestone_row(i) for i in range(40)], [_milestone_row(i) for i in range(40)],
[(i, "todo", 1) for i in range(40)], [(i, "todo", 1, None) for i in range(40)],
[(i, 1000 + i, f"S{i}", "todo") for i in range(40)], [(i, 1000 + i, f"S{i}", "todo") for i in range(40)],
counter=counter, counter=counter,
) )
@@ -190,7 +194,7 @@ async def test_both_step_queries_take_the_same_visibility_clause():
seen.append(uid) seen.append(uid)
return true() return true()
results = [[_milestone_row(409)], [(409, "todo", 1)], [(409, 1, "S", "todo")]] results = [[_milestone_row(409)], [(409, "todo", 1, None)], [(409, 1, "S", "todo")]]
with patch.object(ms, "async_session", _sessions(results, [0])), \ with patch.object(ms, "async_session", _sessions(results, [0])), \
patch.object(ms.access_svc, "readable_notes_clause", _clause): patch.object(ms.access_svc, "readable_notes_clause", _clause):
await ms.get_project_milestone_summaries(7, [5]) await ms.get_project_milestone_summaries(7, [5])
@@ -222,7 +226,7 @@ async def test_the_listing_and_placement_agree_on_what_open_means():
listing_sql: list = [] listing_sql: list = []
rows = await _summaries( rows = await _summaries(
[_milestone_row(409)], [_milestone_row(409)],
[(409, "done", 2), (409, "todo", 2)], [(409, "done", 2, None), (409, "todo", 2, None)],
[(409, s.id, s.title, s.status) for s in steps if s.status in ms.OPEN_STEP_STATUSES], [(409, s.id, s.title, s.status) for s in steps if s.status in ms.OPEN_STEP_STATUSES],
seen_sql=listing_sql, seen_sql=listing_sql,
) )