""" Acceptance tests for Battery Tracker. Each test gets a fresh in-memory SQLite database via the fixtures in conftest.py. The `seeded_client` fixture pre-populates: Devices: Device A (2 slots), Device B (1 slot) Batteries: TST-01 (BrandX, available), TST-02 (BrandY, available), TST-03 (BrandX, retired) """ import pytest # ------------------------------------------------------------------ # # Helpers # ------------------------------------------------------------------ # def get_location(resp): """Return the redirect Location header (without host).""" loc = resp.headers.get("Location", "") # Strip scheme+host if present if loc.startswith("http"): from urllib.parse import urlparse loc = urlparse(loc).path return loc def follow(client, resp): """Follow a single redirect.""" return client.get(get_location(resp)) # ------------------------------------------------------------------ # # Dashboard # ------------------------------------------------------------------ # def test_dashboard_loads(seeded_client): resp = seeded_client.get("/") assert resp.status_code == 200 assert b"TST-01" in resp.data assert b"TST-02" in resp.data assert b"TST-03" in resp.data # ------------------------------------------------------------------ # # Battery — add # ------------------------------------------------------------------ # def test_add_battery(client): resp = client.post("/battery/add", data={"label": "NEW-01", "brand": "TestBrand", "status": "available"}, follow_redirects=True) assert resp.status_code == 200 assert b"NEW-01" in resp.data def test_add_battery_duplicate_label(seeded_client): resp = seeded_client.post("/battery/add", data={"label": "TST-01", "brand": "OtherBrand", "status": "available"}) assert resp.status_code == 400 assert b"already exists" in resp.data def test_add_battery_missing_fields(client): resp = client.post("/battery/add", data={"label": "", "brand": ""}) assert resp.status_code == 400 assert b"required" in resp.data # ------------------------------------------------------------------ # # Battery — detail # ------------------------------------------------------------------ # def test_battery_detail(seeded_client): resp = seeded_client.get("/battery/1") assert resp.status_code == 200 assert b"TST-01" in resp.data assert b"BrandX" in resp.data def test_battery_detail_not_found(client): resp = client.get("/battery/9999") assert resp.status_code == 404 # ------------------------------------------------------------------ # # Battery — edit notes # ------------------------------------------------------------------ # def test_edit_notes(seeded_client): seeded_client.post("/battery/1/edit-notes", data={"notes": "test note here"}) resp = seeded_client.get("/battery/1") assert b"test note here" in resp.data # ------------------------------------------------------------------ # # Battery — assign # ------------------------------------------------------------------ # def test_assign_battery(seeded_client): # TST-01 is battery id=1, Device A is device id=1 resp = seeded_client.post("/battery/1/assign", data={"device_id": "1"}, follow_redirects=True) assert resp.status_code == 200 assert b"Device A" in resp.data def test_assign_retired_battery_blocked(seeded_client): # TST-03 is retired (id=3) resp = seeded_client.post("/battery/3/assign", data={"device_id": "1"}, follow_redirects=True) assert resp.status_code == 200 assert b"retired" in resp.data.lower() def test_assign_over_capacity_blocked(seeded_client): # Fill Device B (1 slot) with TST-01 seeded_client.post("/battery/1/assign", data={"device_id": "2"}) # Try to assign TST-02 to the same full device resp = seeded_client.post("/battery/2/assign", data={"device_id": "2"}, follow_redirects=True) assert resp.status_code == 200 assert b"full" in resp.data.lower() def test_brand_mix_warning(seeded_client): # Assign TST-01 (BrandX) to Device A seeded_client.post("/battery/1/assign", data={"device_id": "1"}) # Assign TST-02 (BrandY) to same Device A → brand mix warning, but succeeds resp = seeded_client.post("/battery/2/assign", data={"device_id": "1"}, follow_redirects=True) assert resp.status_code == 200 # Warning flash should mention mixing assert b"mixing" in resp.data.lower() or b"mix" in resp.data.lower() or b"brand" in resp.data.lower() # ------------------------------------------------------------------ # # Battery — unassign # ------------------------------------------------------------------ # def test_unassign_battery(seeded_client): seeded_client.post("/battery/1/assign", data={"device_id": "1"}) resp = seeded_client.post("/battery/1/unassign", follow_redirects=True) assert resp.status_code == 200 # Battery should show as available on dashboard resp2 = seeded_client.get("/battery/1") assert b"available" in resp2.data.lower() # ------------------------------------------------------------------ # # Battery — retire # ------------------------------------------------------------------ # def test_retire_battery(seeded_client): resp = seeded_client.post("/battery/1/retire", follow_redirects=True) assert resp.status_code == 200 resp2 = seeded_client.get("/battery/1") assert b"retired" in resp2.data.lower() def test_retire_clears_device(seeded_client): seeded_client.post("/battery/1/assign", data={"device_id": "1"}) seeded_client.post("/battery/1/retire") resp = seeded_client.get("/battery/1") assert b"None" in resp.data or b"retired" in resp.data.lower() def test_retire_already_retired(seeded_client): # TST-03 is already retired (id=3) resp = seeded_client.post("/battery/3/retire", follow_redirects=True) assert resp.status_code == 200 assert b"already retired" in resp.data.lower() # ------------------------------------------------------------------ # # Battery — delete # ------------------------------------------------------------------ # def test_delete_battery_confirmation_page(seeded_client): resp = seeded_client.get("/battery/1/delete") assert resp.status_code == 200 assert b"Confirm Delete" in resp.data assert b"TST-01" in resp.data def test_delete_battery(seeded_client): seeded_client.post("/battery/1/delete") resp = seeded_client.get("/battery/1") assert resp.status_code == 404 # ------------------------------------------------------------------ # # Device — list # ------------------------------------------------------------------ # def test_device_list(seeded_client): resp = seeded_client.get("/device/") assert resp.status_code == 200 assert b"Device A" in resp.data assert b"Device B" in resp.data # ------------------------------------------------------------------ # # Device — add # ------------------------------------------------------------------ # def test_add_device(client): resp = client.post("/device/add", data={"name": "My Gadget", "battery_slots": "3"}, follow_redirects=True) assert resp.status_code == 200 assert b"My Gadget" in resp.data def test_add_device_duplicate_name(seeded_client): resp = seeded_client.post("/device/add", data={"name": "Device A", "battery_slots": "2"}) assert resp.status_code == 400 assert b"already exists" in resp.data def test_add_device_missing_name(client): resp = client.post("/device/add", data={"name": "", "battery_slots": "1"}) assert resp.status_code == 400 # ------------------------------------------------------------------ # # Device — detail # ------------------------------------------------------------------ # def test_device_detail(seeded_client): resp = seeded_client.get("/device/1") assert resp.status_code == 200 assert b"Device A" in resp.data assert b"2" in resp.data # battery_slots def test_device_detail_not_found(client): resp = client.get("/device/9999") assert resp.status_code == 404 # ------------------------------------------------------------------ # # Device — delete # ------------------------------------------------------------------ # def test_delete_device_frees_batteries(seeded_client): # Assign TST-01 to Device A seeded_client.post("/battery/1/assign", data={"device_id": "1"}) # Delete Device A resp = seeded_client.post("/device/1/delete", follow_redirects=True) assert resp.status_code == 200 # TST-01 should now be available resp2 = seeded_client.get("/battery/1") assert b"available" in resp2.data.lower() def test_delete_device_removed(seeded_client): seeded_client.post("/device/1/delete") resp = seeded_client.get("/device/1") assert resp.status_code == 404 # ------------------------------------------------------------------ # # Full round-trip # ------------------------------------------------------------------ # def test_add_assign_delete_battery(client): # Add a device client.post("/device/add", data={"name": "Gadget", "battery_slots": "1"}) # Add a battery client.post("/battery/add", data={"label": "RT-01", "brand": "AcmeBrand", "status": "available"}) # Assign it resp = client.post("/battery/1/assign", data={"device_id": "1"}, follow_redirects=True) assert b"Gadget" in resp.data # Delete it client.post("/battery/1/delete") assert client.get("/battery/1").status_code == 404