class TestSettingsApi: def test_list_defaults(self, api): d = api.get("/settings/list").json() keys = {s["key"] for s in d["settings"]} for k in ("amount_min", "amount_max", "shipping_min", "shipping_max", "invoice_format", "invoice_prefix", "default_daily_limit", "attachment_kind", "unsubscribe_enabled", "company_name"): assert k in keys assert all(s["is_default"] for s in d["settings"]) s0 = d["settings"][0] assert {"key", "label", "category", "type", "value", "default"} <= set(s0) def test_save_and_persist(self, api): r = api.post("/settings/save", headers=H(api), json={"key": "amount_min", "value": 25.5}) assert r.status_code == 200, r.text assert r.json()["value"] == 25.5 row = [s for s in api.get("/settings/list").json()["settings"] if s["key"] == "amount_min"][0] assert row["value"] == 25.5 and row["is_default"] is False def test_save_rejects_out_of_range(self, api): r = api.post("/settings/save", headers=H(api), json={"key": "amount_min", "value": -5}) assert r.status_code == 400 r2 = api.post("/settings/save", headers=H(api), json={"key": "default_rate_per_minute", "value": 99999}) assert r2.status_code == 400 def test_save_rejects_unknown_key(self, api): r = api.post("/settings/save", headers=H(api), json={"key": "no_such_setting", "value": 1}) assert r.status_code == 400 def test_bool_and_string_coercion(self, api): r = api.post("/settings/save", headers=H(api), json={"key": "unsubscribe_enabled", "value": "false"}) assert r.status_code == 200 assert r.json()["value"] is False r2 = api.post("/settings/save", headers=H(api), json={"key": "company_name", "value": "Acme Corp"}) assert r2.status_code == 200 assert r2.json()["value"] == "Acme Corp" def test_attachment_kind_whitelist(self, api): ok = api.post("/settings/save", headers=H(api), json={"key": "attachment_kind", "value": "order"}) assert ok.status_code == 200 bad = api.post("/settings/save", headers=H(api), json={"key": "attachment_kind", "value": "garbage"}) assert bad.status_code == 400 def test_invoice_format_requires_tokens(self, api): bad = api.post("/settings/save", headers=H(api), json={"key": "invoice_format", "value": "INV-9999"}) assert bad.status_code == 400 ok = api.post("/settings/save", headers=H(api), json={"key": "invoice_format", "value": "{prefix}{seq4}"}) assert ok.status_code == 200 def test_save_batch_partial_errors(self, api): r = api.post("/settings/save-batch", headers=H(api), json={"values": {"amount_min": 10, "amount_max": 900, "bogus_key": 1, "shipping_min": "nope"}}) assert r.status_code == 200 d = r.json() assert d["applied"] == {"amount_min": 10, "amount_max": 900} assert "bogus_key" in d["errors"] and "shipping_min" in d["errors"] def test_reset_and_reset_all(self, api): api.post("/settings/save", headers=H(api), json={"key": "amount_min", "value": 77}) api.post("/settings/save", headers=H(api), json={"key": "amount_max", "value": 888}) r = api.post("/settings/reset", headers=H(api), json={"key": "amount_min"}) assert r.status_code == 200 row = [s for s in api.get("/settings/list").json()["settings"] if s["key"] == "amount_min"][0] assert row["is_default"] is True r2 = api.post("/settings/reset-all", headers=H(api)) assert r2.status_code == 200 and r2.json()["reset"] == 1 # only amount_max left def test_requires_auth(self, api): from starlette.testclient import TestClient anon = TestClient(api.app) assert anon.get("/settings/list").status_code == 401 r = anon.post("/settings/save", json={"key": "amount_min", "value": 1}) assert r.status_code == 401 def test_csrf_required(self, api): r = api.post("/settings/save", json={"key": "amount_min", "value": 5}) assert r.status_code == 403 # --------------------------------------------------------------------------- # Logs (send attempts, events, audit, DLQ, export) # --------------------------------------------------------------------------- class TestLogsApi: