diff options
| author | Max Nanis | 2026-02-26 15:51:49 -0500 |
|---|---|---|
| committer | Max Nanis | 2026-02-26 15:51:49 -0500 |
| commit | 0bf32fadd85d5938ae29d489efdd82e2cd137300 (patch) | |
| tree | 814e8128947fb604dc7cc3509e72260d95757590 /tests/fixtures/flow.py | |
| parent | 04aee0dc7e908ce020d2d2c3f8ffb4a96424b883 (diff) | |
| download | amt-jb-0bf32fadd85d5938ae29d489efdd82e2cd137300.tar.gz amt-jb-0bf32fadd85d5938ae29d489efdd82e2cd137300.zip | |
Passing Managers into flow tasks for better pytest usage. Conftests broken out into seperate fixture files. Extensive type hinting.
Diffstat (limited to 'tests/fixtures/flow.py')
| -rw-r--r-- | tests/fixtures/flow.py | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/tests/fixtures/flow.py b/tests/fixtures/flow.py new file mode 100644 index 0000000..633e05d --- /dev/null +++ b/tests/fixtures/flow.py @@ -0,0 +1,296 @@ +from datetime import timezone, datetime +from typing import Dict, Callable, Any, Optional +from uuid import uuid4 + +import pytest +import requests +from generalresearchutils.models.thl.payout import UserPayoutEvent +from generalresearchutils.models.thl.wallet import PayoutType +from generalresearchutils.models.thl.wallet.cashout_method import ( + CashoutRequestResponse, + CashoutRequestInfo, +) +from mypy_boto3_mturk.type_defs import ( + GetHITResponseTypeDef, + GetAssignmentResponseTypeDef, +) + +from jb.config import settings +from jb.managers.amt import ( + APPROVAL_MESSAGE, + BONUS_MESSAGE, +) +from generalresearchutils.currency import USDCent +from generalresearchutils.models.thl.definitions import PayoutStatus + + +@pytest.fixture +def approved_assignment_stubs( + get_assignment_response: GetAssignmentResponseTypeDef, + get_assignment_response_approved: Callable[[str], GetAssignmentResponseTypeDef], + amt_assignment_id: str, + amt_hit_id: str, + get_hit_response_reviewing: GetHITResponseTypeDef, +) -> Callable[..., list[Dict[str, Any]]]: + + # These are the AMT_CLIENT stubs/mocks that need to be set when running + # process_assignment_submitted() which will result in an approved + # assignment and sent bonus + def _inner( + feedback: str = APPROVAL_MESSAGE, + override_response: Optional[str] = None, + override_approve_response: Optional[str] = None, + ) -> list[Dict[str, Any]]: + + response = override_response or get_assignment_response + approve_response = ( + override_approve_response or get_assignment_response_approved(feedback) + ) + + return [ + { + "operation": "get_assignment", + "response": response, + "expected_params": {"AssignmentId": amt_assignment_id}, + }, + { + "operation": "approve_assignment", + "response": {}, + "expected_params": { + "AssignmentId": amt_assignment_id, + "RequesterFeedback": feedback, + "OverrideRejection": False, + }, + }, + { + "operation": "get_assignment", + "response": approve_response, + "expected_params": {"AssignmentId": amt_assignment_id}, + }, + { + "operation": "update_hit_review_status", + "response": {}, + "expected_params": {"HITId": amt_hit_id, "Revert": False}, + }, + { + "operation": "get_hit", + "response": get_hit_response_reviewing, + "expected_params": {"HITId": amt_hit_id}, + }, + ] + + return _inner + + +@pytest.fixture +def approved_assignment_stubs_w_bonus( + approved_assignment_stubs: Callable[..., list[Dict[str, Any]]], + amt_worker_id: str, + amt_assignment_id: str, + pe_id: str, +) -> list[Dict[str, Any]]: + + now = datetime.now(tz=timezone.utc) + stubs = approved_assignment_stubs().copy() + stubs.append( + { + "operation": "send_bonus", + "response": {}, + "expected_params": { + "WorkerId": amt_worker_id, + "BonusAmount": "0.07", + "AssignmentId": amt_assignment_id, + "Reason": BONUS_MESSAGE, + "UniqueRequestToken": pe_id, + }, + } + ) + stubs.append( + { + "operation": "list_bonus_payments", + "response": { + "BonusPayments": [ + { + "WorkerId": amt_worker_id, + "BonusAmount": "0.07", + "AssignmentId": amt_assignment_id, + "Reason": BONUS_MESSAGE, + "GrantTime": now, + } + ] + }, + "expected_params": {"AssignmentId": amt_assignment_id}, + } + ) + return stubs + + +@pytest.fixture +def rejected_assignment_stubs( + assignment_response: GetAssignmentResponseTypeDef, + assignment_response_factory_rejected: Callable[[str], GetAssignmentResponseTypeDef], + amt_assignment_id: str, + amt_hit_id: str, + hit_response_reviewing: GetHITResponseTypeDef, +) -> Callable[..., list[Dict[str, Any]]]: + + # These are the AMT_CLIENT stubs/mocks that need to be set when running + # process_assignment_submitted() which will result in a rejected + # assignment + def _inner( + reject_reason: str, + override_response: Optional[str] = None, + override_reject_response: Optional[str] = None, + ) -> list[Dict[str, Any]]: + + response = override_response or assignment_response + reject_response = ( + override_reject_response + or assignment_response_factory_rejected(reject_reason) + ) + + return [ + { + "operation": "get_assignment", + "response": response, + "expected_params": {"AssignmentId": amt_assignment_id}, + }, + { + "operation": "reject_assignment", + "response": {}, + "expected_params": { + "AssignmentId": amt_assignment_id, + "RequesterFeedback": reject_reason, + }, + }, + { + "operation": "get_assignment", + "response": reject_response, + "expected_params": {"AssignmentId": amt_assignment_id}, + }, + { + "operation": "update_hit_review_status", + "response": {}, + "expected_params": {"HITId": amt_hit_id, "Revert": False}, + }, + { + "operation": "get_hit", + "response": hit_response_reviewing, + "expected_params": {"HITId": amt_hit_id}, + }, + ] + + return _inner + + +@pytest.fixture +def mock_thl_responses( + monkeypatch: pytest.MonkeyPatch, amt_worker_id: str, tsid: str, pe_id: str +) -> Callable[..., None]: + + original_get = requests.get + original_post = requests.post + + class MockThlCashoutRequestResponse: + def json(self): + return CashoutRequestResponse( + status="success", + cashout=CashoutRequestInfo( + id=pe_id, + description="amt something", + status=PayoutStatus.PENDING, + ), + ).model_dump(mode="json") + + def _inner( + user_blocked: bool = False, + status_finished: bool = True, + status_complete: bool = False, + wallet_redeemable_amount: int = 10, + ): + + def mock_get(url, *args, **kwargs): # type: ignore + profile_url = f"{settings.fsb_host}{settings.product_id}/user/{amt_worker_id}/profile/" + status_url = f"{settings.fsb_host}{settings.product_id}/status/{tsid}/" + cashout_request_url = f"{settings.fsb_host}{settings.product_id}/cashout/" + wallet_url = f"{settings.fsb_host}{settings.product_id}/wallet/" + if url == profile_url: + + class MockThlProfileResponse: + def json(self): + return {"user_profile": {"user": {"blocked": user_blocked}}} + + return MockThlProfileResponse() + + elif url == wallet_url: + + class MockThlWalletResponse: + def json(self) -> Dict[str, Any]: + return { + "wallet": {"redeemable_amount": wallet_redeemable_amount} + } + + return MockThlWalletResponse() + + elif url == status_url: + + class MockThlStatusResponse: + def json(self) -> Dict[str, Any]: + return { + "tsid": tsid, + "product_id": str(settings.product_id), + "product_user_id": amt_worker_id, + "started": "2020-06-02T00:30:35.036398Z", + "finished": ( + "2020-06-02T00:31:35.036398Z" + if status_finished + else None + ), + "status": 3 if status_complete else 2, + "payout": 10 if status_complete else 0, + "user_payout": 10 if status_complete else 0, + "status_code_1": "BUYER_FAIL", + "status_code_2": None, + } + + return MockThlStatusResponse() + + elif url == cashout_request_url: + return MockThlCashoutRequestResponse() + + else: + raise ValueError(f"unhandled call: {url=} {args=} {kwargs=}") + + return original_get(url, *args, **kwargs) + + def mock_post(url, *args, **kwargs): # type: ignore + cashout_request_url = f"{settings.fsb_host}{settings.product_id}/cashout/" + manage_cashout_request_url = f"{settings.fsb_host}{settings.fsb_host_private_route}/thl/manage_cashout/" + + if url == cashout_request_url: + return MockThlCashoutRequestResponse() + + elif url == manage_cashout_request_url: + json = kwargs["json"] + print(json) + payout_id = json["payout_id"] + new_status = json["new_status"] + + class MockThlManageCashoutResponse: + def json(self): + return UserPayoutEvent( + uuid=payout_id, + status=new_status, + amount=USDCent(5), + debit_account_uuid=uuid4().hex, + cashout_method_uuid=uuid4().hex, + payout_type=PayoutType.AMT, + ).model_dump(mode="json") + + return MockThlManageCashoutResponse() + return original_post(url, *args, **kwargs) + + monkeypatch.setattr(requests, "get", mock_get) + monkeypatch.setattr(requests, "post", mock_post) + + return _inner |
