diff --git a/tests/test_acceptance.py b/tests/test_acceptance.py index 6e10acb..096541d 100644 --- a/tests/test_acceptance.py +++ b/tests/test_acceptance.py @@ -1216,3 +1216,113 @@ def test_device_charge_all_parent_no_installed(client): ) assert resp.status_code == 200 assert b"No installed batteries" in resp.data + + +# ------------------------------------------------------------------ # +# Battery — unretire +# ------------------------------------------------------------------ # + +def test_unretire_battery(seeded_client): + # id=3 is retired by fixture + resp = seeded_client.post("/battery/3/unretire", follow_redirects=True) + assert resp.status_code == 200 + assert b"now available again" in resp.data + assert b"available" in seeded_client.get("/battery/3").data.lower() + + +def test_unretire_not_retired(seeded_client): + resp = seeded_client.post("/battery/1/unretire", follow_redirects=True) + assert b"is not retired" in resp.data + + +# ------------------------------------------------------------------ # +# Logbook +# ------------------------------------------------------------------ # + +def test_battery_logbook_add_and_delete(seeded_client): + resp = seeded_client.post("/battery/1/logbook/add", + data={"body": "Swapped terminals"}, follow_redirects=True) + assert b"Swapped terminals" in resp.data + resp = seeded_client.post("/battery/1/logbook/1/delete", follow_redirects=True) + assert b"Logbook entry deleted" in resp.data + assert b"Swapped terminals" not in seeded_client.get("/battery/1").data + + +def test_battery_logbook_add_empty_body(seeded_client): + resp = seeded_client.post("/battery/1/logbook/add", + data={"body": " "}, follow_redirects=True) + assert b"Entry text is required" in resp.data + + +def test_device_logbook_add_and_delete(seeded_client): + resp = seeded_client.post("/device/1/logbook/add", + data={"body": "Cleaned contacts"}, follow_redirects=True) + assert b"Cleaned contacts" in resp.data + resp = seeded_client.post("/device/1/logbook/1/delete", follow_redirects=True) + assert b"Logbook entry deleted" in resp.data + assert b"Cleaned contacts" not in seeded_client.get("/device/1").data + + +def test_device_logbook_add_empty_body(seeded_client): + resp = seeded_client.post("/device/1/logbook/add", + data={"body": ""}, follow_redirects=True) + assert b"Entry text is required" in resp.data + + +# ------------------------------------------------------------------ # +# Charge log — delete +# ------------------------------------------------------------------ # + +def test_charge_log_delete_reverts_cycles(seeded_client): + seeded_client.post("/battery/1/charge-log/add", + data={"charged_date": "2026-06-01", "increment_cycles": "1"}) + data = _json.loads(seeded_client.get("/export/all.json").data) + assert next(b for b in data["batteries"] if b["id"] == 1)["charge_cycles"] == 1 + log_id = data["charge_logs"][0]["id"] + resp = seeded_client.post(f"/battery/1/charge-log/{log_id}/delete", + follow_redirects=True) + assert b"Charge log entry deleted" in resp.data + data = _json.loads(seeded_client.get("/export/all.json").data) + assert next(b for b in data["batteries"] if b["id"] == 1)["charge_cycles"] == 0 + assert data["charge_logs"] == [] + + +# ------------------------------------------------------------------ # +# CSV exports +# ------------------------------------------------------------------ # + +def test_csv_export_routes(seeded_client): + expectations = { + "/export/batteries.csv": b"label", + "/export/devices.csv": b"parent_id,parent_name", + "/export/charge-logs.csv": b"charged_date", + "/export/capacity-tests.csv": b"tested_capacity_mah", + "/export/pct-logs.csv": b"percentage", + } + for url, marker in expectations.items(): + resp = seeded_client.get(url) + assert resp.status_code == 200, url + assert "text/csv" in resp.content_type, url + assert marker in resp.data, url + resp = seeded_client.get("/export/csv.zip") + assert resp.status_code == 200 + assert "zip" in resp.content_type + + +# ------------------------------------------------------------------ # +# Sub-component edit — type/location stay NULL +# ------------------------------------------------------------------ # + +def test_subcomponent_edit_resave_keeps_nulls(client): + _setup_rc_car(client) + # device 2 = Remote, sub-component of 1; re-save smuggling in type/location + resp = client.post("/device/2/edit", data={ + "name": "Remote", "battery_slots": "2", "parent_id": "1", + "device_type": "Toy", "location": "Garage", "battery_size": "AA", + }, follow_redirects=True) + assert resp.status_code == 200 + data = _json.loads(client.get("/export/all.json").data) + rem = next(d for d in data["devices"] if d["name"] == "Remote") + assert rem["parent_id"] is not None + assert rem["device_type"] is None + assert rem["location"] is None