import pytest
import copy
from datetime import datetime, timedelta
from typing import Callable
from uuid import uuid4
from dateutil.tz import tzlocal
from mypy_boto3_mturk.type_defs import (
GetHITResponseTypeDef,
CreateHITTypeResponseTypeDef,
ResponseMetadataTypeDef,
CreateHITWithHITTypeResponseTypeDef,
GetAssignmentResponseTypeDef,
)
from jb.managers.amt import APPROVAL_MESSAGE, NO_WORK_APPROVAL_MESSAGE
from tests import generate_amt_id
# --- Generic Boto ---
@pytest.fixture
def amt_response_metadata() -> ResponseMetadataTypeDef:
req_id = str(uuid4())
return {
"RequestId": req_id,
"HTTPStatusCode": 200,
"HTTPHeaders": {
"x-amzn-requestid": req_id,
"content-type": "application/x-amz-json-1.1",
"content-length": "46",
"date": "Wed, 15 Oct 2025 02:16:16 GMT",
},
"RetryAttempts": 0,
}
# --- Assignment ---
@pytest.fixture
def assignment_response(
amt_hit_id: str,
amt_assignment_id: str,
amt_worker_id: str,
hit_response: GetHITResponseTypeDef,
amt_response_metadata: ResponseMetadataTypeDef,
tsid: str,
) -> GetAssignmentResponseTypeDef:
hit_response = hit_response["HIT"]
local_now = datetime.now(tz=tzlocal())
return {
"Assignment": {
"AssignmentId": amt_assignment_id,
"WorkerId": amt_worker_id,
"HITId": amt_hit_id,
"AssignmentStatus": "Submitted",
"AutoApprovalTime": local_now + timedelta(days=7),
"AcceptTime": local_now - timedelta(minutes=10),
"SubmitTime": local_now,
"Deadline": local_now + timedelta(minutes=90),
"Answer": '\n'
'\n '
"\n amt_worker_id\n "
f" {amt_worker_id}\n \n \n "
" amt_assignment_id\n "
f" {amt_assignment_id}\n \n \n "
f" tsid\n {tsid}\n "
" \n",
"RequesterFeedback": "Good work",
},
"HIT": hit_response,
"ResponseMetadata": amt_response_metadata,
}
@pytest.fixture
def assignment_response_bad_tsid(
assignment_response: GetAssignmentResponseTypeDef,
amt_worker_id: str,
amt_assignment_id: str,
) -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response)
res["Assignment"]["Answer"] = (
'\n'
'\n '
"\n amt_worker_id\n "
f" {amt_worker_id}\n \n \n "
" amt_assignment_id\n "
f" {amt_assignment_id}\n \n "
f" \n tsid\n abc123 \n \n"
f""
)
return res
@pytest.fixture
def assignment_response_no_tsid(
assignment_response: GetAssignmentResponseTypeDef,
amt_worker_id: str,
amt_assignment_id: str,
) -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response)
res["Assignment"]["Answer"] = (
'\n'
'\n '
"\n amt_worker_id\n "
f" {amt_worker_id}\n \n \n "
" amt_assignment_id\n "
f" {amt_assignment_id}\n \n "
# f"\n tsid\n {tsid}\n \n"
f""
)
return res
@pytest.fixture
def assignment_response_approved_no_tsid(
assignment_response_no_tsid: GetAssignmentResponseTypeDef,
) -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response_no_tsid)
res["Assignment"]["AssignmentStatus"] = "Approved"
res["Assignment"]["RequesterFeedback"] = NO_WORK_APPROVAL_MESSAGE
res["Assignment"]["ApprovalTime"] = res["Assignment"]["SubmitTime"]
return res
@pytest.fixture
def assignment_response_factory_approved(
assignment_response: GetAssignmentResponseTypeDef,
) -> Callable[[str], GetAssignmentResponseTypeDef]:
def inner(feedback: str = APPROVAL_MESSAGE) -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response)
res["Assignment"]["AssignmentStatus"] = "Approved"
res["Assignment"]["RequesterFeedback"] = feedback
res["Assignment"]["ApprovalTime"] = res["Assignment"]["SubmitTime"]
return res
return inner
@pytest.fixture
def assignment_response_factory_rejected(
assignment_response: GetAssignmentResponseTypeDef,
) -> Callable[[str], GetAssignmentResponseTypeDef]:
def inner(reject_reason: str = "reject reason") -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response)
res["Assignment"]["AssignmentStatus"] = "Rejected"
res["Assignment"]["RequesterFeedback"] = reject_reason
res["Assignment"]["RejectionTime"] = res["Assignment"]["SubmitTime"]
return res
return inner
@pytest.fixture
def assignment_response_factory_rejected_no_tsid(
assignment_response_no_tsid: GetAssignmentResponseTypeDef,
) -> Callable[[str], GetAssignmentResponseTypeDef]:
def inner(reject_reason: str = "reject reason") -> GetAssignmentResponseTypeDef:
res = copy.deepcopy(assignment_response_no_tsid)
res["Assignment"]["AssignmentStatus"] = "Rejected"
res["Assignment"]["RequesterFeedback"] = reject_reason
res["Assignment"]["RejectionTime"] = res["Assignment"]["SubmitTime"]
return res
return inner
# --- HITType ---
@pytest.fixture
def create_hit_type_response(
amt_hit_type_id: str, amt_response_metadata: ResponseMetadataTypeDef
) -> CreateHITTypeResponseTypeDef:
return {
"HITTypeId": amt_hit_type_id,
"ResponseMetadata": amt_response_metadata,
}
# --- HIT ---
@pytest.fixture
def hit_response_with_hit_type(
amt_hit_type_id: str,
amt_hit_id: str,
amt_response_metadata: ResponseMetadataTypeDef,
) -> CreateHITWithHITTypeResponseTypeDef:
amt_group_id = generate_amt_id(length=30)
return {
"HIT": {
"HITId": amt_hit_id,
"HITTypeId": amt_hit_type_id,
"HITGroupId": amt_group_id,
"CreationTime": datetime(2025, 10, 14, 20, 22, tzinfo=tzlocal()),
"Title": "Test",
"Description": "test",
"Question": '\n\n https://jamesbillings67.com/work/\n 1200\n ',
"HITStatus": "Assignable",
"MaxAssignments": 1,
"Reward": "0.05",
"AutoApprovalDelayInSeconds": 2_592_000,
"Expiration": datetime(2025, 10, 14, 20, 24, 3, tzinfo=tzlocal()),
"AssignmentDurationInSeconds": 123,
"QualificationRequirements": [],
"HITReviewStatus": "NotReviewed",
"NumberOfAssignmentsPending": 0,
"NumberOfAssignmentsAvailable": 1,
"NumberOfAssignmentsCompleted": 0,
},
"ResponseMetadata": amt_response_metadata,
}
@pytest.fixture
def hit_response(
amt_hit_type_id: str,
amt_hit_id: str,
amt_response_metadata: ResponseMetadataTypeDef,
) -> GetHITResponseTypeDef:
amt_group_id = generate_amt_id(length=30)
return {
"HIT": {
"HITId": amt_hit_id,
"HITTypeId": amt_hit_type_id,
"HITGroupId": amt_group_id,
"CreationTime": datetime(2025, 10, 13, 23, 0, 3, tzinfo=tzlocal()),
"Title": "Awesome Surveys!",
"Description": "Give us your opinion",
"Question": '\n\n https://jamesbillings67.com/work/\n 1200\n ',
"Keywords": "market,research,amazing",
"HITStatus": "Assignable",
"MaxAssignments": 1,
"Reward": "0.05",
"AutoApprovalDelayInSeconds": 604_800,
"Expiration": datetime(2025, 10, 27, 23, 0, 3, tzinfo=tzlocal()),
"AssignmentDurationInSeconds": 5_400,
"QualificationRequirements": [],
"HITReviewStatus": "NotReviewed",
"NumberOfAssignmentsPending": 0,
"NumberOfAssignmentsAvailable": 1,
"NumberOfAssignmentsCompleted": 0,
},
"ResponseMetadata": amt_response_metadata,
}
@pytest.fixture
def hit_response_reviewing(
hit_response: GetHITResponseTypeDef,
) -> GetHITResponseTypeDef:
res = copy.deepcopy(hit_response)
res["HIT"]["NumberOfAssignmentsAvailable"] = 0
res["HIT"]["NumberOfAssignmentsCompleted"] = 1
res["HIT"]["HITStatus"] = "Reviewing"
return res