Skip to content

Commit

Permalink
Majority POST (not Put) test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
ManicJamie committed Apr 14, 2024
1 parent 3bdb1ad commit 7673b7d
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 40 deletions.
27 changes: 16 additions & 11 deletions src/speedruncompy/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def is_compliant_type(hint: type):
"""Whether the type of the response should be coerced to the hint"""
hint = degrade_union(hint, _OptFieldMarker, NoneType)
if get_origin(hint) == list: return False
return issubclass(hint, Datatype) or issubclass(hint, Enum) or hint == float
return issubclass(hint, Datatype) or issubclass(hint, Enum) or hint == float or hint == bool

def is_type(value, hint: type):
if value is None:
Expand Down Expand Up @@ -111,7 +111,8 @@ def enforce_types(self):
else: missing_fields.append(fieldname) # Non-optional fields must be present, report if not
elif is_compliant_type(true_type):
if not isinstance(self[fieldname], true_type):
self[fieldname] = true_type(raw) # Coerce compliant types
if not(true_type is bool and (self[fieldname] > 1 or self[fieldname] < 0)):
self[fieldname] = true_type(raw) # Coerce compliant types, exclude bool if larger than 1
elif get_origin(true_type) is list:
list_type = get_args(true_type)[0]
if is_compliant_type(list_type): # Coerce list types
Expand All @@ -121,12 +122,14 @@ def enforce_types(self):
attr = self[fieldname]
if true_type == Any: _log.debug(f"Undocumented attr {fieldname} has value {raw} of type {type(raw)}")
elif not is_type(attr, hint):
if STRICT_TYPE_CONFORMANCE: raise AttributeError(f"Datatype {type(self).__name__}'s attribute {fieldname} expects {nullable_type} but received {type(attr).__name__}")
else: _log.warning(f"Datatype {type(self).__name__}'s attribute {attr} expects {nullable_type} but received {type(self[attr]).__name__}")
msg = f"Datatype {type(self).__name__}'s attribute {fieldname} expects {nullable_type} but received {type(attr).__name__} = {attr}"
if STRICT_TYPE_CONFORMANCE: raise AttributeError(msg)
else: _log.warning(msg)

if len(missing_fields) > 0:
if STRICT_TYPE_CONFORMANCE: raise IncompleteDatatype(f"Datatype {type(self).__name__} constructed missing mandatory fields {missing_fields}")
else: _log.warning(f"Datatype {type(self).__name__} constructed missing mandatory fields {missing_fields}")
msg = f"Datatype {type(self).__name__} constructed missing mandatory fields {missing_fields}"
if STRICT_TYPE_CONFORMANCE: raise IncompleteDatatype(msg)
else: _log.warning(msg)


# Allow interacting with these types as if they were dicts (in all reasonable ways)
Expand Down Expand Up @@ -935,17 +938,18 @@ class ThemeSettings(Datatype):
primaryColor: str
panelColor: str
panelOpacity: int
navbarColor: str
navbarColor: int # enum
backgroundColor: str
backgroundFit: bool
backgroundPosition: int # enum
backgroundRepeat: bool #TODO: check
backgroundRepeat: int # enum
backgroundScrolling: bool #TODO: check
foregroundFit: bool
foregroundPosition: int # enum
foregroundRepeat: bool #TODO: check
foregroundScrolling: bool #TODO: check
staticAssets: list[StaticAsset]
staticAssetUpdates: list[StaticAsset] # TODO: check optional

class ThreadReadStatus(Datatype):
threadId: str
Expand All @@ -958,6 +962,7 @@ class Ticket(Datatype):
status: int # enum
requestorId: str
dateSubmitted: int
dateResolved: OptField[int]
metadata: str
"""This is a json object that may be dependent on type"""

Expand All @@ -966,7 +971,7 @@ class UserBlock(Datatype):
blockeeId: str

class NotificationSetting(Datatype):
type: Any #TODO: check
type: int # enum
gameId: OptField[str]
site: bool
email: bool
Expand All @@ -980,8 +985,8 @@ class UserSettings(Datatype):
powerLevel: SitePowerLevel
areaId: str
theme: str # TODO: check what happens w/ custom theme
color1id: str
color2id: str
color1Id: str
color2Id: str
colorAnimate: int # enum
avatarDecoration: dict #TODO: enabled: bool
defaultView: int # enum
Expand Down
1 change: 1 addition & 0 deletions src/speedruncompy/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,7 @@ def perform_async(self, retries=5, delay=1, **kwargs) -> Coroutine[Any, Any, r_P

# Thread Actions
class GetThreadReadStatus(PostRequest):
"""NB: when called without auth will return an empty list"""
def __init__(self, threadIds: list[str], **params) -> None:
super().__init__("GetThreadReadStatus", returns=r_GetThreadReadStatus, threadIds=threadIds, **params)

Expand Down
9 changes: 6 additions & 3 deletions src/speedruncompy/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class r_GetUserLeaderboard(Datatype):
players: list[Player]
values: list[Value]
variables: list[Variable]
followedGameIds: None
followedGameIds: Optional[Any] #TODO: find if this is ever not null
"""Unused null key"""
challengeList: list[Challenge]
challengeRunList: list[ChallengeRun]
Expand Down Expand Up @@ -295,6 +295,8 @@ class r_GetTickets(Datatype):
pagination: Pagination
userList: list[User]
gameList: list[Game]
userModCountList: list[Any] #TODO: document
userRunCountList: list[Any] #TODO: document

class r_GetUserBlocks(Datatype):
userBlocks: list[UserBlock]
Expand All @@ -307,8 +309,9 @@ class r_GetUserSettings(Datatype):
userSocialConnectionList: list[UserSocialConnection]
gameList: list[Game]
themeList: list[Theme]
supporterCreditList: Any #TODO: document
supportCodeList: Any #TODO: document
titleList: list[Title]
supporterCreditList: list[Any] #TODO: document
supporterCodeList: list[Any] #TODO: document
supporterSubscription: OptField[Any]
experimentList: Any
enabledExperimentIds: Any
Expand Down
79 changes: 53 additions & 26 deletions test/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
comment_list_type = itemType.RUN
thread_id = "mbkmj"
user_id = "j4r6pwm8" # ManicJamie (must have leaderboard)
user_url = "manicjamie"
forum_id = "gz5lqd21" # Hollow Knight
guide_id = "ew8s2" # Hollow Knight code of conduct
article_id = "jd5y09v2"
Expand Down Expand Up @@ -450,83 +451,109 @@ def test_GetModerationRuns_unauthed(self):
GetModerationRuns(gameId=game_id, verified=verified.PENDING).perform()

def test_GetNotifications(self):
result = result = GetNotifications(_api=self.api).perform()
result = GetNotifications(_api=self.api).perform()
log_result(result)
check_datatype_coverage(result)

def test_GetNotifications_paginated(self):
result = result = GetNotifications(_api=self.api).perform_all()
result = GetNotifications(_api=self.api).perform_all()
log_result(result)
check_datatype_coverage(result)

def test_GetNotifications_paginated_raw(self):
result = result = GetNotifications(_api=self.api)._perform_all_raw()
result = GetNotifications(_api=self.api)._perform_all_raw()
log_result(result)
check_pages(result)

@pytest.mark.skip(reason="Test stub")
def test_GetNotifications_unauthed(self):
result = ...
log_result(result)
check_datatype_coverage(result)
with pytest.raises(Unauthorized):
GetNotifications().perform()

@pytest.mark.skip(reason="Test stub")
def test_GetRunSettings(self):
result = ...
result = GetRunSettings(_api=self.api, runId=run_id).perform()
log_result(result)
check_datatype_coverage(result)

def test_GetRunSettings_unauthed(self):
with pytest.raises(Unauthorized):
GetRunSettings(runId=run_id).perform()

@pytest.mark.skip(reason="Insufficient auth to test")
@pytest.mark.skip(reason="Test stub")
def test_GetSeriesSettings(self):
result = ...
result = GetSeriesSettings()
log_result(result)
check_datatype_coverage(result)

@pytest.mark.skip(reason="Test stub")
def test_GetThemeSettings(self):
result = ...
result = GetThemeSettings(_api=self.api, userId=user_id).perform()
log_result(result)
check_datatype_coverage(result)

@pytest.mark.skip(reason="Test stub")
def test_GetThreadReadStatus(self):
result = ...
result = GetThreadReadStatus(_api=self.api, threadIds=[thread_id]).perform()
log_result(result)
check_datatype_coverage(result)

def test_GetThreadReadStatus_unauthed(self):
result = GetThreadReadStatus(threadIds=[thread_id]).perform()
log_result(result)
check_datatype_coverage(result)

@pytest.mark.skip(reason="Test stub")

def test_GetTickets(self):
result = ...
result = GetTickets(_api=self.api, requestorIds=[user_id]).perform()
log_result(result)
check_datatype_coverage(result)
@pytest.mark.skip(reason="Test stub")

#TODO: GetTickets depagination
def test_GetUserBlocks(self):
result = ...
result = GetUserBlocks(_api=self.api).perform()
log_result(result)
check_datatype_coverage(result)

@pytest.mark.skip(reason="Test stub")
def test_GetUserBlocks_unauthed(self):
with pytest.raises(Unauthorized):
GetUserBlocks().perform()

def test_GetUserSettings(self):
result = ...
result = GetUserSettings(_api=self.api, userUrl=user_url).perform()
log_result(result)
check_datatype_coverage(result)

def test_GetUserSettings_unauthed(self):
with pytest.raises(Unauthorized):
GetUserSettings(userUrl=user_url).perform()

@pytest.mark.skip(reason="Test stub")
def test_GetUserSupporterData(self):
result = ...
result = GetUserSupporterData(_api=self.api, userUrl=user_url).perform()
log_result(result)
check_datatype_coverage(result)

def test_GetUserSupporterData_unauthed(self):
with pytest.raises(Unauthorized):
GetUserSupporterData(userUrl=user_url).perform()

class TestPutRequests():
"""Requests that modify site data"""
api = SpeedrunComPy("Test")
api.set_phpsessid(SESSID)

low_api = SpeedrunComPy("Test_LOWAUTH")
low_api.set_phpsessid(LOW_SESSID)

@pytest.mark.skip(reason="Unreasonable to test this endpoint as it would create spam.")
def test_PutAuthSignup(self):
...

@pytest.mark.skip(reason="Test stub")
def test_PutComment_Flow(self):
"""Posts and then deletes a comment"""
result = ...
log_result(result)
check_datatype_coverage(result)

@pytest.mark.skip(reason="Test stub")
def test_PutComment(self):
result = ...
delete_result = ...
log_result(result)
check_datatype_coverage(result)

Expand Down

0 comments on commit 7673b7d

Please sign in to comment.