diff --git a/src/pylibrelinkup/models/login.py b/src/pylibrelinkup/models/login.py index 175a191..a293b80 100644 --- a/src/pylibrelinkup/models/login.py +++ b/src/pylibrelinkup/models/login.py @@ -1,6 +1,6 @@ from typing import List -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator __all__ = [ "Llu", @@ -26,8 +26,21 @@ class Llu(BaseModel): touAccept: int = Field(default=0) +class HistoryItem(BaseModel): + policyAccept: int = Field(default=0) + declined: bool | None = None + + +class RealWorldEvidence(BaseModel): + policyAccept: int = Field(default=0) + declined: bool = False + touAccept: int = Field(default=0) + history: List[HistoryItem] = [] + + class Consents(BaseModel): - llu: Llu + llu: Llu = Llu() + realWorldEvidence: RealWorldEvidence = RealWorldEvidence() class SystemMessages(BaseModel): @@ -88,6 +101,10 @@ class Data(BaseModel): authTicket: AuthTicket invitations: List[str] + @field_validator("invitations", mode="before") + def coerce_null_to_empty_list(cls, v): + return v if v is not None else [] + class LoginResponse(BaseModel): status: int = Field(default=0) diff --git a/tests/data/realworldevidence_consent_response.json b/tests/data/realworldevidence_consent_response.json new file mode 100644 index 0000000..d274cf3 --- /dev/null +++ b/tests/data/realworldevidence_consent_response.json @@ -0,0 +1,82 @@ +{ + "status": 0, + "data": { + "user": { + "id": "", + "firstName": "", + "lastName": "", + "email": "", + "country": "GB", + "uiLanguage": "en-GB", + "communicationLanguage": "en-GB", + "accountType": "pat", + "uom": "0", + "dateFormat": "2", + "timeFormat": "2", + "emailDay": [ + 1 + ], + "system": { + "messages": { + "appReviewBanner": 1704319358, + "firstUsePhoenix": 1704288789, + "firstUsePhoenixReportsDataMerged": 1704288789, + "lluGettingStartedBanner": 1704319384, + "lluNewFeatureModal": 1704319219, + "lvWebPostRelease": "3.16.19", + "streamingTourMandatory": 1704319433 + } + }, + "details": {}, + "twoFactor": { + "primaryMethod": "phone", + "primaryValue": "", + "secondaryMethod": "email", + "secondaryValue": "" + }, + "created": 1704288789, + "lastLogin": 1737891119, + "programs": {}, + "dateOfBirth": 29894400, + "practices": {}, + "devices": { + "": { + "id": "", + "nickname": "", + "sn": "", + "type": 40066, + "uploadDate": 1737891498 + } + }, + "consents": { + "realWorldEvidence": { + "policyAccept": 1737890544, + "declined": true, + "touAccept": 0, + "history": [ + { + "policyAccept": 1704288796 + }, + { + "policyAccept": 1737890544, + "declined": true + } + ] + } + } + }, + "messages": { + "unread": 0 + }, + "notifications": { + "unresolved": 0 + }, + "authTicket": { + "token": "parp", + "expires": 1753443551, + "duration": 15552000000 + }, + "invitations": null, + "trustedDeviceToken": "" + } +} \ No newline at end of file diff --git a/tests/test_client_authentication.py b/tests/test_client_authentication.py index 8107d8e..26a3642 100644 --- a/tests/test_client_authentication.py +++ b/tests/test_client_authentication.py @@ -142,3 +142,18 @@ def test_redirection_response_raises_redirect_error( with pytest.raises(RedirectError): pylibrelinkup_client.client.authenticate() + + +def test_realworldevidence_consent_in_login_response( + mocked_responses, pylibrelinkup_client, get_response_json +): + """Test that the authenticate method raises an error when the user needs to accept the real world evidence consent.""" + + mocked_responses.add( + responses.POST, + f"{pylibrelinkup_client.api_url.value}/llu/auth/login", + json=get_response_json("realworldevidence_consent_response.json"), + status=200, + ) + + pylibrelinkup_client.client.authenticate()