From 28d097cfbf09ed043b0fde5cedaaae6308627a68 Mon Sep 17 00:00:00 2001 From: archeious Date: Fri, 17 Apr 2026 12:51:36 -0600 Subject: [PATCH] test: cover notes add, update, clear, snapshot copy, and no-deviation Service tests hit create, update, missing-id, blank-collapses-to-null, and the snapshot copying behaviour. Route tests hit both pages' create flows, the budget notes endpoint, and the month update route. A dedicated assertion confirms that changing notes on a month entry does not flip the deviation state: notes are free-form annotation, not a signal of plan drift. Refs #13 Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_notes.py | 159 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/test_notes.py diff --git a/tests/test_notes.py b/tests/test_notes.py new file mode 100644 index 0000000..498a526 --- /dev/null +++ b/tests/test_notes.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +from decimal import Decimal + +from quartermaster import month_service, service +from quartermaster.models import Section + + +def test_add_entry_stores_notes(db): + entry = service.add_entry( + db, + Section.sinking_fund, + "Emergency", + Decimal("500.00"), + notes="Target: 3 months of expenses", + ) + assert entry.notes == "Target: 3 months of expenses" + + +def test_add_entry_strips_and_nulls_empty_notes(db): + entry = service.add_entry( + db, Section.other, "Gift", Decimal("25.00"), notes=" " + ) + assert entry.notes is None + + +def test_set_entry_notes_updates(db): + entry = service.add_entry(db, Section.food, "Groceries", Decimal("400.00")) + updated = service.set_entry_notes(db, entry.id, "weekly Costco run") + assert updated is not None + assert updated.notes == "weekly Costco run" + + +def test_set_entry_notes_missing_returns_none(db): + assert service.set_entry_notes(db, 9999, "oops") is None + + +def test_snapshot_copies_notes(db): + service.add_entry( + db, + Section.sinking_fund, + "Emergency", + Decimal("500.00"), + notes="3 months expenses", + ) + month = month_service.create_month(db, "2026-04") + emergency = next(e for e in month.entries if e.origin_name == "Emergency") + assert emergency.notes == "3 months expenses" + + +def test_month_notes_edit_does_not_change_deviation_state(db): + service.add_entry( + db, Section.fixed_bill, "Rent", Decimal("1200.00"), notes="auto-pay" + ) + month = month_service.create_month(db, "2026-04") + rent = next(e for e in month.entries if e.origin_name == "Rent") + # unchanged initially + assert ( + month_service.deviation_state(rent) == month_service.DeviationState.unchanged + ) + # update notes only + month_service.update_month_entry( + db, month, rent.id, notes="auto-pay; renew July 2027" + ) + db.refresh(rent) + assert rent.notes == "auto-pay; renew July 2027" + assert ( + month_service.deviation_state(rent) == month_service.DeviationState.unchanged + ) + + +def test_update_month_entry_notes_to_empty_nulls(db): + service.add_entry(db, Section.other, "Parking", Decimal("25.00"), notes="work") + month = month_service.create_month(db, "2026-04") + entry = next(e for e in month.entries if e.origin_name == "Parking") + assert entry.notes == "work" + month_service.update_month_entry(db, month, entry.id, notes="") + db.refresh(entry) + assert entry.notes is None + + +# --- route-level ----------------------------------------------------------- + + +def test_create_entry_route_accepts_notes(client): + response = client.post( + "/sections/sinking_fund/entries", + data={"name": "Emergency", "amount": "500.00", "notes": "3 mo cushion"}, + ) + assert response.status_code == 200 + assert "3 mo cushion" in response.text + + +def test_update_entry_notes_route(client): + client.post( + "/sections/food/entries", + data={"name": "Groceries", "amount": "400.00"}, + ) + response = client.post("/entries/1/notes", data={"notes": "weekly"}) + assert response.status_code == 200 + assert "weekly" in response.text + + +def test_update_entry_notes_empty_clears(client): + client.post( + "/sections/food/entries", + data={"name": "Groceries", "amount": "400.00", "notes": "weekly"}, + ) + response = client.post("/entries/1/notes", data={"notes": ""}) + assert response.status_code == 200 + # the input's value="" still renders but the placeholder kicks in; + # specifically, no literal "weekly" anymore + assert "value=\"weekly\"" not in response.text + + +def test_create_month_entry_route_accepts_notes(client): + client.post("/month/2026-04/create") + response = client.post( + "/month/2026-04/sections/other/entries", + data={"name": "Gift", "planned": "25.00", "notes": "birthday"}, + ) + assert response.status_code == 200 + assert "birthday" in response.text + + +def test_update_month_entry_route_accepts_notes(client): + client.post( + "/sections/fixed_bill/entries", + data={"name": "Rent", "amount": "1200.00"}, + ) + client.post("/month/2026-04/create") + response = client.post( + "/month/2026-04/entries/1", data={"notes": "auto-pay"} + ) + assert response.status_code == 200 + assert "auto-pay" in response.text + + +def test_budget_page_renders_notes_inputs(client): + client.post( + "/sections/fixed_bill/entries", + data={"name": "Rent", "amount": "1200.00", "notes": "due 1st"}, + ) + response = client.get("/") + assert response.status_code == 200 + assert "entry-notes-row" in response.text + assert "due 1st" in response.text + + +def test_month_page_renders_notes_inputs(client): + client.post( + "/sections/fixed_bill/entries", + data={"name": "Rent", "amount": "1200.00", "notes": "auto-pay"}, + ) + client.post("/month/2026-04/create") + response = client.get("/month/2026-04") + assert response.status_code == 200 + assert "entry-notes-row" in response.text + assert "auto-pay" in response.text