# retoor from datetime import datetime, timezone import requests from devplacepy.database import get_table, refresh_snapshot from devplacepy.services.openai_gateway.usage import GATEWAY_LEDGER from devplacepy.utils import generate_uid from tests.api.admin.gateway.index import ( JSON_gateway, admin_session, member_key, ) from tests.conftest import BASE_URL RESETS_URL = f"{BASE_URL}/admin/gateway/quota-resets" _counter = [0] def _owner(): _counter[0] += 1 return f"gwquota{_counter[0]}-{generate_uid()}" def _burn(owner_id, app_reference, cost): refresh_snapshot() get_table(GATEWAY_LEDGER).insert( { "uid": generate_uid(), "owner_kind": "user", "owner_id": owner_id, "app_reference": app_reference, "cost_usd": cost, "created_at": datetime.now(timezone.utc).isoformat(), } ) refresh_snapshot() def _spent(owner_id, app_reference): from devplacepy.services.openai_gateway import quota refresh_snapshot() quota._QUOTA_CACHE.clear() return quota.spent_24h("user", owner_id, app_reference) def test_quota_reset_requires_admin(seeded_db): assert ( requests.post(RESETS_URL, headers=JSON_gateway, allow_redirects=False).status_code == 401 ) key = member_key() assert ( requests.post( RESETS_URL, headers={**JSON_gateway, "X-API-KEY": key}, json={}, allow_redirects=False, ).status_code == 403 ) def test_admin_can_reset_a_scoped_spend(seeded_db): owner = _owner() _burn(owner, "appa", 2.5) assert _spent(owner, "appa") == 2.5 response = admin_session(seeded_db).post( RESETS_URL, json={"owner_kind": "user", "owner_id": owner, "app_reference": "appa"}, ) assert response.status_code == 200 assert response.json()["ok"] is True assert _spent(owner, "appa") == 0.0 def test_the_reset_response_echoes_the_scope(seeded_db): owner = _owner() _burn(owner, "appa", 1.0) payload = admin_session(seeded_db).post( RESETS_URL, json={"owner_kind": "user", "owner_id": owner, "app_reference": "appa"}, ).json()["reset"] assert payload["owner_kind"] == "user" assert payload["owner_id"] == owner assert payload["app_reference"] == "appa" assert payload["reset_at"] def test_a_reset_leaves_another_caller_capped(seeded_db): first, second = _owner(), _owner() _burn(first, "appa", 2.0) _burn(second, "appa", 2.0) admin_session(seeded_db).post( RESETS_URL, json={"owner_kind": "user", "owner_id": first, "app_reference": "appa"}, ) assert _spent(first, "appa") == 0.0 assert _spent(second, "appa") == 2.0 def test_a_reset_keeps_the_usage_history(seeded_db): owner = _owner() _burn(owner, "appa", 2.0) before = get_table(GATEWAY_LEDGER).count(owner_id=owner) admin_session(seeded_db).post( RESETS_URL, json={"owner_kind": "user", "owner_id": owner, "app_reference": "appa"}, ) refresh_snapshot() assert get_table(GATEWAY_LEDGER).count(owner_id=owner) == before def test_an_unscoped_reset_clears_every_caller(seeded_db): owner = _owner() _burn(owner, "appa", 2.0) assert admin_session(seeded_db).post(RESETS_URL, json={}).status_code == 200 assert _spent(owner, "appa") == 0.0 def test_an_unknown_owner_kind_is_rejected(seeded_db): response = admin_session(seeded_db).post(RESETS_URL, json={"owner_kind": "wizard"}) assert response.status_code == 400 assert response.json()["ok"] is False def test_a_malformed_app_reference_is_rejected(seeded_db): response = admin_session(seeded_db).post( RESETS_URL, json={"app_reference": "not a valid app!"} ) assert response.status_code == 400 def test_the_reset_is_audited(seeded_db): owner = _owner() _burn(owner, "appa", 1.0) admin = admin_session(seeded_db) admin.post( RESETS_URL, json={"owner_kind": "user", "owner_id": owner, "app_reference": "appa"}, ) refresh_snapshot() entries = admin.get( f"{BASE_URL}/admin/audit-log", headers=JSON_gateway, params={"event_key": "gateway.quota.reset"}, ).json()["entries"] assert any(entry.get("target_type") == "gateway_quota" for entry in entries) def test_spend_recorded_after_a_reset_counts_again(seeded_db): owner = _owner() _burn(owner, "appa", 2.0) admin_session(seeded_db).post( RESETS_URL, json={"owner_kind": "user", "owner_id": owner, "app_reference": "appa"}, ) _burn(owner, "appa", 0.5) assert _spent(owner, "appa") == 0.5