|
# retoor <retoor@molodetz.nl>
|
|
|
|
import base64
|
|
import io
|
|
from datetime import datetime, timezone
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
from devplacepy.db_client import get_award_usage, get_table, init_db
|
|
from devplacepy.db_client import recompute_user_award_stats
|
|
from devplacepy.services.jobs.award_service import AwardService
|
|
from devplacepy.utils import generate_uid, make_combined_slug
|
|
from tests.conftest import run_async
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _db(local_db, tmp_path, monkeypatch):
|
|
init_db()
|
|
monkeypatch.setattr("devplacepy.attachments.ATTACHMENTS_DIR", tmp_path / "attachments")
|
|
yield
|
|
|
|
|
|
def _png_bytes():
|
|
buf = io.BytesIO()
|
|
Image.new("RGBA", (64, 64), (40, 80, 120, 255)).save(buf, format="PNG")
|
|
return buf.getvalue()
|
|
|
|
|
|
class _FakeResponse:
|
|
def __init__(self, payload, headers=None):
|
|
self._payload = payload
|
|
self.headers = headers or {}
|
|
|
|
def raise_for_status(self):
|
|
return None
|
|
|
|
def json(self):
|
|
return self._payload
|
|
|
|
|
|
class _FakeClient:
|
|
def __init__(self, *args, **kwargs):
|
|
self._png = _png_bytes()
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def post(self, url, json=None, headers=None):
|
|
encoded = base64.b64encode(self._png).decode("ascii")
|
|
return _FakeResponse(
|
|
{"data": [{"b64_json": encoded}]},
|
|
headers={
|
|
"X-Gateway-Cost-USD": "0.001",
|
|
"X-Gateway-Prompt-Tokens": "10",
|
|
"X-Gateway-Completion-Tokens": "0",
|
|
"X-Gateway-Total-Tokens": "10",
|
|
"X-Gateway-Upstream-Latency-Ms": "100",
|
|
"X-Gateway-Total-Latency-Ms": "120",
|
|
},
|
|
)
|
|
|
|
|
|
def _seed_pending(giver_uid, receiver_uid, description="Nice job"):
|
|
uid = generate_uid()
|
|
slug = make_combined_slug(description, uid)
|
|
get_table("awards").insert(
|
|
{
|
|
"uid": uid,
|
|
"slug": slug,
|
|
"description": description,
|
|
"giver_uid": giver_uid,
|
|
"receiver_uid": receiver_uid,
|
|
"attachment_uid_512": "",
|
|
"attachment_uid_256": "",
|
|
"attachment_uid_64": "",
|
|
"generated_at": None,
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"job_uid": "",
|
|
"deleted_at": None,
|
|
"deleted_by": None,
|
|
}
|
|
)
|
|
return uid, slug
|
|
|
|
|
|
def _user(prefix):
|
|
uid = generate_uid()
|
|
get_table("users").insert(
|
|
{
|
|
"uid": uid,
|
|
"username": f"{prefix}_{uid[:8]}",
|
|
"email": f"{uid[:8]}@t.dev",
|
|
"api_key": generate_uid(),
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
}
|
|
)
|
|
return uid
|
|
|
|
|
|
def _job(award_uid, giver_uid, receiver_uid, api_key):
|
|
return {
|
|
"uid": generate_uid(),
|
|
"payload": {
|
|
"award_uid": award_uid,
|
|
"giver_uid": giver_uid,
|
|
"receiver_uid": receiver_uid,
|
|
"description": "Nice job",
|
|
"api_key": api_key,
|
|
},
|
|
}
|
|
|
|
|
|
def test_process_finalizes_award(monkeypatch):
|
|
monkeypatch.setattr("devplacepy.services.jobs.award_service.stealth.stealth_sync_client", _FakeClient)
|
|
giver = _user("giver")
|
|
receiver = _user("recv")
|
|
api_key = get_table("users").find_one(uid=giver)["api_key"]
|
|
award_uid, _ = _seed_pending(giver, receiver)
|
|
run_async(AwardService().process(_job(award_uid, giver, receiver, api_key)))
|
|
row = get_table("awards").find_one(uid=award_uid)
|
|
assert row.get("generated_at")
|
|
assert row.get("attachment_uid_512")
|
|
assert row.get("attachment_uid_256")
|
|
assert row.get("attachment_uid_64")
|
|
recompute_user_award_stats(receiver)
|
|
user = get_table("users").find_one(uid=receiver)
|
|
assert user.get("award_count") == 1
|
|
|
|
|
|
def test_process_is_idempotent(monkeypatch):
|
|
monkeypatch.setattr("devplacepy.services.jobs.award_service.stealth.stealth_sync_client", _FakeClient)
|
|
giver = _user("giver2")
|
|
receiver = _user("recv2")
|
|
api_key = get_table("users").find_one(uid=giver)["api_key"]
|
|
award_uid, _ = _seed_pending(giver, receiver)
|
|
job = _job(award_uid, giver, receiver, api_key)
|
|
svc = AwardService()
|
|
run_async(svc.process(job))
|
|
first = get_table("awards").find_one(uid=award_uid)
|
|
run_async(svc.process(job))
|
|
second = get_table("awards").find_one(uid=award_uid)
|
|
assert first["generated_at"] == second["generated_at"]
|
|
assert first["attachment_uid_512"] == second["attachment_uid_512"]
|
|
|
|
|
|
def test_process_skips_soft_deleted_award(monkeypatch):
|
|
monkeypatch.setattr("devplacepy.services.jobs.award_service.stealth.stealth_sync_client", _FakeClient)
|
|
giver = _user("giver3")
|
|
receiver = _user("recv3")
|
|
api_key = get_table("users").find_one(uid=giver)["api_key"]
|
|
award_uid, _ = _seed_pending(giver, receiver)
|
|
get_table("awards").update(
|
|
{"uid": award_uid, "deleted_at": datetime.now(timezone.utc).isoformat(), "deleted_by": giver},
|
|
["uid"],
|
|
)
|
|
result = run_async(AwardService().process(_job(award_uid, giver, receiver, api_key)))
|
|
assert result.get("skipped") is True
|
|
assert not get_table("awards").find_one(uid=award_uid).get("generated_at")
|
|
|
|
|
|
def test_award_usage_accumulates_from_gateway_headers(monkeypatch):
|
|
monkeypatch.setattr("devplacepy.services.jobs.award_service.stealth.stealth_sync_client", _FakeClient)
|
|
get_table("award_usage").delete()
|
|
giver = _user("giver4")
|
|
receiver = _user("recv4")
|
|
api_key = get_table("users").find_one(uid=giver)["api_key"]
|
|
award_uid, _ = _seed_pending(giver, receiver)
|
|
run_async(AwardService().process(_job(award_uid, giver, receiver, api_key)))
|
|
usage = get_award_usage()
|
|
assert usage["calls"] >= 1
|
|
assert usage["cost_usd"] > 0 |