aboutsummaryrefslogtreecommitdiff
path: root/tests/http
diff options
context:
space:
mode:
authorMax Nanis2026-02-21 02:15:52 -0500
committerMax Nanis2026-02-21 02:15:52 -0500
commit67ab724561e4ceb8fe8fb4031de277168f7d9724 (patch)
tree4d85619973491e7239f0e83dc5cdd85618f0f248 /tests/http
parentaf8057d58ff152f511f5161a7626b0fffa9d661a (diff)
downloadamt-jb-67ab724561e4ceb8fe8fb4031de277168f7d9724.tar.gz
amt-jb-67ab724561e4ceb8fe8fb4031de277168f7d9724.zip
More pytest conf, some views, and defining more attrs on the settings config
Diffstat (limited to 'tests/http')
-rw-r--r--tests/http/test_notifications.py71
-rw-r--r--tests/http/test_status.py78
-rw-r--r--tests/http/test_statuses.py102
3 files changed, 251 insertions, 0 deletions
diff --git a/tests/http/test_notifications.py b/tests/http/test_notifications.py
new file mode 100644
index 0000000..70458b8
--- /dev/null
+++ b/tests/http/test_notifications.py
@@ -0,0 +1,71 @@
+import json
+
+import pytest
+from httpx import AsyncClient
+import secrets
+
+from jb.config import JB_EVENTS_STREAM, settings
+from jb.decorators import REDIS
+from jb.models.event import MTurkEvent
+from tests import generate_amt_id
+
+
+def generate_hex_id(length: int = 40) -> str:
+ # length is number of hex chars, so we need length//2 bytes
+ return secrets.token_hex(length // 2)
+
+
+@pytest.fixture
+def example_mturk_event_body(amt_hit_id, amt_hit_type_id, amt_assignment_id):
+ return {
+ "Type": "Notification",
+ "Message": json.dumps(
+ {
+ "Events": [
+ {
+ "EventType": "AssignmentSubmitted",
+ "EventTimestamp": "2025-10-16T18:45:51.000000Z",
+ "HITId": amt_hit_id,
+ "AssignmentId": amt_assignment_id,
+ "HITTypeId": amt_hit_type_id,
+ }
+ ],
+ "EventDocId": generate_hex_id(),
+ "SourceAccount": settings.aws_owner_id,
+ "CustomerId": generate_amt_id(length=14),
+ "EventDocVersion": "2006-05-05",
+ }
+ ),
+ }
+
+
+@pytest.fixture()
+def clean_mturk_events_redis_stream():
+ REDIS.xtrim(JB_EVENTS_STREAM, maxlen=0)
+ assert REDIS.xlen(JB_EVENTS_STREAM) == 0
+ yield
+ REDIS.xtrim(JB_EVENTS_STREAM, maxlen=0)
+ assert REDIS.xlen(JB_EVENTS_STREAM) == 0
+
+
+@pytest.mark.anyio
+async def test_mturk_notifications(
+ httpxclient: AsyncClient,
+ no_limit,
+ example_mturk_event_body,
+ amt_assignment_id,
+ clean_mturk_events_redis_stream,
+):
+ client = httpxclient
+
+ res = await client.post(url=f"/{settings.sns_path}/", json=example_mturk_event_body)
+ res.raise_for_status()
+
+ msg_res = REDIS.xread(streams={JB_EVENTS_STREAM: 0}, count=1, block=100)
+ msg_res = msg_res[0][1][0]
+ msg_id, msg = msg_res
+ REDIS.xdel(JB_EVENTS_STREAM, msg_id)
+
+ msg_json = msg["data"]
+ event = MTurkEvent.model_validate_json(msg_json)
+ assert event.amt_assignment_id == amt_assignment_id
diff --git a/tests/http/test_status.py b/tests/http/test_status.py
new file mode 100644
index 0000000..d88ff65
--- /dev/null
+++ b/tests/http/test_status.py
@@ -0,0 +1,78 @@
+from uuid import uuid4
+
+import pytest
+from httpx import AsyncClient
+
+from jb.config import settings
+from tests import generate_amt_id
+
+
+@pytest.mark.anyio
+async def test_get_status_args(httpxclient: AsyncClient, no_limit):
+ client = httpxclient
+
+ # tsid misformatted
+ res = await client.get(f"/status/{uuid4().hex[:-1]}/")
+ assert res.status_code == 422
+ assert "String should have at least 32 characters" in res.text
+
+
+@pytest.mark.anyio
+async def test_get_status_error(httpxclient: AsyncClient, no_limit):
+ # Expects settings.fsb_host to point to a functional thl-fsb
+ client = httpxclient
+
+ # tsid doesn't exist
+ res = await client.get(f"/status/{uuid4().hex}/")
+ assert res.status_code == 502
+ assert res.json()["detail"] == "Failed to fetch status"
+
+
+@pytest.mark.anyio
+async def test_get_status_complete(httpxclient: AsyncClient, no_limit, mock_requests):
+ client = httpxclient
+
+ tsid = uuid4().hex
+ url = f"{settings.fsb_host}{settings.product_id}/status/{tsid}/"
+
+ mock_response = {
+ "tsid": tsid,
+ "product_id": settings.product_id,
+ "bpuid": generate_amt_id(length=21),
+ "started": "2022-06-29T23:43:48.247777Z",
+ "finished": "2022-06-29T23:56:57.632634Z",
+ "status": 3,
+ "payout": 81,
+ "user_payout": 77,
+ "payout_format": "${payout/100:.2f}",
+ "user_payout_string": "$0.77",
+ "kwargs": {},
+ }
+ mock_requests.get(url, json=mock_response, status_code=200)
+ res = await client.get(f"/status/{tsid}/")
+ assert res.status_code == 200
+ assert res.json() == {"status": 3, "payout": "$0.77"}
+
+
+@pytest.mark.anyio
+async def test_get_status_failure(httpxclient: AsyncClient, no_limit, mock_requests):
+ client = httpxclient
+
+ tsid = uuid4().hex
+ url = f"{settings.fsb_host}{settings.product_id}/status/{tsid}/"
+
+ mock_response = {
+ "tsid": tsid,
+ "product_id": settings.product_id,
+ "bpuid": "123ABC",
+ "status": 2,
+ "payout": 0,
+ "user_payout": 0,
+ "payout_format": "${payout/100:.2f}",
+ "user_payout_string": None,
+ "kwargs": {},
+ }
+ mock_requests.get(url, json=mock_response, status_code=200)
+ res = await client.get(f"/status/{tsid}/")
+ assert res.status_code == 200
+ assert res.json() == {"status": 2, "payout": None}
diff --git a/tests/http/test_statuses.py b/tests/http/test_statuses.py
new file mode 100644
index 0000000..ffc98fd
--- /dev/null
+++ b/tests/http/test_statuses.py
@@ -0,0 +1,102 @@
+from datetime import datetime, timezone, timedelta
+from urllib.parse import urlencode
+
+import pytest
+from uuid import uuid4
+from httpx import AsyncClient
+
+from jb.config import settings
+
+
+@pytest.mark.anyio
+async def test_get_statuses(httpxclient: AsyncClient, no_limit, amt_worker_id):
+ # Expects settings.fsb_host to point to a functional thl-fsb
+ client = httpxclient
+ now = datetime.now(tz=timezone.utc)
+
+ params = {"worker_id": amt_worker_id}
+ res = await client.get(f"/statuses/", params=params)
+ assert res.status_code == 200
+ assert res.json() == []
+
+ params = {"worker_id": amt_worker_id, "started_after": now.isoformat()}
+ res = await client.get(f"/statuses/", params=params)
+ assert res.status_code == 422
+ assert "Input should be a valid integer" in res.text
+
+
+@pytest.fixture
+def fsb_get_statuses_example_response(amt_worker_id, tsid1, tsid2):
+ return {
+ "tasks_status": [
+ {
+ "tsid": tsid1,
+ "product_id": settings.product_id,
+ "bpuid": amt_worker_id,
+ "started": "2025-06-12T03:27:24.902280Z",
+ "finished": "2025-06-12T03:29:37.626481Z",
+ "status": 2,
+ "payout": 0,
+ "user_payout": None,
+ "payout_format": None,
+ "user_payout_string": None,
+ "kwargs": {},
+ "status_code_1": "SESSION_START_QUALITY_FAIL",
+ "status_code_2": "ENTRY_URL_MODIFICATION",
+ },
+ {
+ "tsid": tsid2,
+ "product_id": settings.product_id,
+ "bpuid": amt_worker_id,
+ "started": "2025-06-12T03:30:18.176826Z",
+ "finished": "2025-06-12T03:36:58.789059Z",
+ "status": 2,
+ "payout": 0,
+ "user_payout": None,
+ "payout_format": None,
+ "user_payout_string": None,
+ "kwargs": {},
+ "status_code_1": "BUYER_QUALITY_FAIL",
+ "status_code_2": None,
+ },
+ ]
+ }
+
+
+@pytest.mark.anyio
+async def test_get_statuses_mock(
+ httpxclient: AsyncClient,
+ no_limit,
+ amt_worker_id,
+ mock_requests,
+ fsb_get_statuses_example_response,
+ tsid1,
+ tsid2,
+):
+ client = httpxclient
+ now = datetime.now(tz=timezone.utc)
+ started_after = now - timedelta(minutes=5)
+
+ # The fsb call we are mocking ------v
+ params = {
+ "bpuid": amt_worker_id,
+ "started_after": round(started_after.timestamp()),
+ "started_before": round(now.timestamp()),
+ }
+ url = f"{settings.fsb_host}{settings.product_id}/status/" + "?" + urlencode(params)
+ mock_requests.get(url, json=fsb_get_statuses_example_response, status_code=200)
+ # ---- end mock
+
+ params = {
+ "worker_id": amt_worker_id,
+ "started_after": round(started_after.timestamp()),
+ "started_before": round(now.timestamp()),
+ }
+ result = await client.get(f"/statuses/", params=params)
+ assert result.status_code == 200
+ res = result.json()
+ assert len(res) == 2
+ assert res == [
+ {"status": 2, "tsid": tsid1},
+ {"status": 2, "tsid": tsid2},
+ ]