fix(advice): an editing token, so a comment-only correction is safe too
The gravity guard caught a correction that changed the gravity, but a
COMMENT-ONLY correction leaves the gravity unchanged - so the late
thread for the old advice still matched and overwrote the fresh one.
Reproduced against the live database:
fresh advice matched: 1
stale thread matched: 1 <-- should be 0
FINAL advice: 'STALE ADVICE'
Every edit now writes a fresh token onto the reading, and the advice
thread carries the token it was given. A late thread finds a token that
no longer matches, so its write is refused. The gravity is still
checked as well.
Verified against the live database:
token changed: True
fresh advice matched: 1
stale thread matched: 0 <-- must be 0
FINAL advice: 'FRESH ADVICE'
174 tests (up from 173).
This commit is contained in:
@@ -600,6 +600,46 @@ def test_stale_advice_cannot_overwrite_a_correction(repo):
|
||||
)
|
||||
|
||||
|
||||
def test_a_comment_only_correction_cannot_be_overwritten(repo):
|
||||
"""A comment-only correction leaves the gravity unchanged.
|
||||
|
||||
So the gravity alone cannot tell the two generations apart - a late
|
||||
thread for the old advice would still match and overwrite the fresh
|
||||
one. The editing token is what tells them apart.
|
||||
"""
|
||||
# Written through upsert_reading, so it has an editing token.
|
||||
reading_id = repo.upsert_reading(beer_run_id="1", beer_type="Ale", sg=1050,
|
||||
reading_date="2026-01-01",
|
||||
final_reading=False, comment="first comment")
|
||||
doc = repo.readings.find_one({"beer_run_id": "1"})
|
||||
token = doc.get("advice_token")
|
||||
assert token, "the reading has no editing token"
|
||||
|
||||
# The advice thread starts on this generation.
|
||||
# The brewer corrects the COMMENT only - the gravity is unchanged.
|
||||
repo.upsert_reading(beer_run_id="1", beer_type="Ale", sg=1050,
|
||||
reading_date="2026-01-01", final_reading=False,
|
||||
comment="corrected comment")
|
||||
assert repo.readings.find_one({"beer_run_id": "1"}).get("advice_token") != token, (
|
||||
"the correction did not write a fresh token"
|
||||
)
|
||||
|
||||
# The fresh advice lands.
|
||||
fresh_token = repo.readings.find_one({"beer_run_id": "1"}).get("advice_token")
|
||||
assert repo.set_reading_advice(reading_id, "FRESH ADVICE", True,
|
||||
expected_sg=1050,
|
||||
expected_token=fresh_token) == 1
|
||||
|
||||
# The late thread for the OLD generation finally lands.
|
||||
stale = repo.set_reading_advice(reading_id, "STALE ADVICE", True,
|
||||
expected_sg=1050, expected_token=token)
|
||||
assert stale == 0, "the stale advice overwrote the correction"
|
||||
|
||||
after = repo.readings.find_one({"beer_run_id": "1"})
|
||||
assert after["advice"] == "FRESH ADVICE"
|
||||
assert after["comment"] == "corrected comment"
|
||||
|
||||
|
||||
def test_a_reply_cut_off_at_the_token_ceiling_is_marked(repo):
|
||||
"""A reply that stopped at the token limit is incomplete.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user