Files
UsageKiosk/tests/test_devin.py
T
nessi 58eab24258 chore: initial project setup with AI usage dashboard for Codex and Devin
Add FastAPI-based monitoring dashboard with support for Codex CLI and Devin API v3 Enterprise consumption tracking. Includes Docker deployment, systemd service configuration, kiosk mode launcher, comprehensive documentation, and demo mode for testing without credentials.
2026-09-18 11:34:33 +02:00

126 lines
5.0 KiB
Python

import time
import httpx
import pytest
from app.config import Settings
from app.providers.devin import DevinProvider
def make_client(handler):
return httpx.AsyncClient(transport=httpx.MockTransport(handler), base_url='https://api.devin.ai')
@pytest.mark.asyncio
async def test_org_consumption_and_max_limit():
now = int(time.time())
async def handler(request):
if request.url.path.endswith('/cycles'):
return httpx.Response(200, json={'items': [{'after': now - 10, 'before': now + 1000}]})
if '/daily/organizations/' in request.url.path:
return httpx.Response(200, json={'total_acus': 124, 'consumption_by_date': []})
if request.url.path.endswith('/organizations/org/id'):
return httpx.Response(200, json={'max_cycle_acu_limit': 320})
return httpx.Response(404)
client = make_client(handler)
provider = DevinProvider(Settings(devin_org_id='org/id'), client)
result = await provider.get_usage()
assert result.status == 'ok'
limit = result.limits[0]
assert limit.used == 124
assert limit.limit == 320
assert limit.remaining == 196
assert limit.used_percent == pytest.approx(38.75)
await client.aclose()
@pytest.mark.asyncio
async def test_user_scope_wins_over_org_scope():
now = int(time.time())
calls = []
async def handler(request):
calls.append(request.url.path)
if request.url.path.endswith('/cycles'):
return httpx.Response(200, json={'items': [{'after': now - 10, 'before': now + 1000}]})
if '/daily/users/' in request.url.path:
return httpx.Response(200, json={'total_acus': 12, 'consumption_by_date': []})
if '/organizations/' in request.url.path:
return httpx.Response(200, json={'max_cycle_acu_limit': 320})
return httpx.Response(404)
client = make_client(handler)
provider = DevinProvider(Settings(devin_user_id='user/id', devin_org_id='org/id'), client)
result = await provider.get_usage()
assert result.status == 'ok'
assert result.limits[0].limit is None
assert any('/daily/users/' in path for path in calls)
assert not any('/v3/enterprise/organizations/' in path for path in calls)
await client.aclose()
@pytest.mark.asyncio
async def test_cycle_pagination_uses_end_cursor():
now = int(time.time())
cycle_queries = []
async def handler(request):
if request.url.path.endswith('/cycles'):
cycle_queries.append(request.url.params)
if len(cycle_queries) == 1:
return httpx.Response(200, json={'items': [{'after': now - 1000, 'before': now - 100}],
'has_next_page': True, 'end_cursor': 'next'})
return httpx.Response(200, json={'items': [{'after': now - 10, 'before': now + 1000}]})
return httpx.Response(200, json={'total_acus': 12, 'consumption_by_date': []})
client = make_client(handler)
provider = DevinProvider(Settings(), client)
result = await provider.get_usage()
assert result.status == 'ok'
assert len(cycle_queries) == 2
assert cycle_queries[1].get('after') == 'next'
await client.aclose()
@pytest.mark.asyncio
async def test_user_scope_does_not_invent_limit():
now = int(time.time())
async def handler(request):
if request.url.path.endswith('/cycles'):
return httpx.Response(200, json={'items': [{'after': now - 10, 'before': now + 1000}]})
return httpx.Response(200, json={'total_acus': 12, 'consumption_by_date': []})
client = make_client(handler)
provider = DevinProvider(Settings(devin_user_id='user/id'), client)
result = await provider.get_usage()
assert result.status == 'ok'
assert result.limits[0].limit is None
assert result.limits[0].used == 12
await client.aclose()
@pytest.mark.asyncio
async def test_required_forbidden_is_generic():
async def handler(request):
return httpx.Response(403, text='secret upstream body')
client = make_client(handler)
provider = DevinProvider(Settings(), client)
result = await provider.get_usage()
assert result.status == 'error'
assert result.error == 'Unable to retrieve usage information'
assert 'secret' not in result.model_dump_json()
await client.aclose()
@pytest.mark.asyncio
async def test_optional_org_forbidden_keeps_consumption():
now = int(time.time())
async def handler(request):
if request.url.path.endswith('/cycles'):
return httpx.Response(200, json={'items': [{'after': now - 10, 'before': now + 1000}]})
if '/daily/organizations/' in request.url.path:
return httpx.Response(200, json={'total_acus': 12, 'consumption_by_date': []})
return httpx.Response(403, text='no org read')
client = make_client(handler)
provider = DevinProvider(Settings(devin_org_id='org'), client)
result = await provider.get_usage()
assert result.status == 'ok'
assert result.limits[0].used == 12
assert result.limits[0].limit is None
await client.aclose()