vhi-python 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: vhi-python
3
+ Version: 0.1.0
4
+ Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
5
+ Author-email: Agent <agent@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/agent/vhi-python
8
+ Project-URL: Repository, https://github.com/agent/vhi-python.git
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
18
+ Requires-Dist: responses>=0.23.0; extra == "dev"
19
+
20
+ # Vhi Python Client
21
+
22
+ A Python client library for interacting dynamically with the Vhi API. Provides automated handling of Session state, Multi-Factor Authentication (MFA) callbacks, Claims Retrieval, and memory-efficient Document Downloads.
23
+
24
+ ## Installation
25
+
26
+ You can install the library directly from source (or via pip once published):
27
+
28
+ ```bash
29
+ pip install vhi-python
30
+ ```
31
+
32
+ Alternatively, to install it locally for development:
33
+
34
+ ```bash
35
+ git clone https://github.com/agent/vhi-python.git
36
+ cd vhi-python
37
+ pip install -e .
38
+ ```
39
+
40
+ ## Quick Start Example
41
+
42
+ This example demonstrates how to use the library to log in, handle an MFA callback (simulated via CLI input), retrieve claims, and download a claim PDF.
43
+
44
+ ```python
45
+ import os
46
+ from vhi.client import VhiClient
47
+ from vhi.exceptions import VhiAuthenticationError, VhiApiError
48
+
49
+ def my_cli_mfa_callback() -> str:
50
+ """
51
+ A simple callback function that pauses execution
52
+ and waits for the user to input the 2FA code sent to their phone/email.
53
+ """
54
+ return input("Vhi 2FA Code Required. Enter OTP: ").strip()
55
+
56
+ def main():
57
+ # 1. Initialize the client
58
+ # Replace with your actual credentials
59
+ username = os.getenv("VHI_USERNAME", "your_email@example.com")
60
+ password = os.getenv("VHI_PASSWORD", "your_password")
61
+
62
+ # We pass our callback function so the client can automatically
63
+ # pause and invoke it if the server demands MFA.
64
+ client = VhiClient(
65
+ username=username,
66
+ password=password,
67
+ mfa_callback=my_cli_mfa_callback
68
+ )
69
+
70
+ try:
71
+ # 2. Login
72
+ print("Logging in...")
73
+ client.login()
74
+ print("Login successful!")
75
+
76
+ # 3. Retrieve Claims
77
+ print("\nRetrieving claims...")
78
+ statements = client.get_claims()
79
+
80
+ if not statements:
81
+ print("No claim statements found.")
82
+ return
83
+
84
+ for claim in statements:
85
+ print(f"- Claim {claim.claim_id} | Date: {claim.date_of_service} | Status: {claim.status}")
86
+
87
+ # 4. Download a Document
88
+ # We will download the first available document as an example
89
+ first_claim = statements[0]
90
+ if first_claim.document_id:
91
+ download_path = f"{first_claim.claim_id}.pdf"
92
+ print(f"\nDownloading document {first_claim.document_id} to {download_path}...")
93
+ client.download_document(first_claim.document_id, download_path)
94
+ print("Download complete!")
95
+
96
+ except VhiAuthenticationError as e:
97
+ print(f"Authentication failed: {e}")
98
+ except VhiApiError as e:
99
+ print(f"API Error occurred: {e}")
100
+
101
+ if __name__ == "__main__":
102
+ main()
103
+ ```
104
+
105
+ ## Error Handling
106
+
107
+ The library provides structural custom exceptions:
108
+ - `VhiAuthenticationError`: Raised when login fails due to invalid credentials.
109
+ - `VhiMfaRequiredError`: Raised when the server requests MFA but you didn't provide a callback, or when the callback returns an invalid OTP.
110
+ - `VhiApiError`: Raised for any other 4xx or 5xx API errors. Contains standard HTTP status codes for debugging.
@@ -0,0 +1,91 @@
1
+ # Vhi Python Client
2
+
3
+ A Python client library for interacting dynamically with the Vhi API. Provides automated handling of Session state, Multi-Factor Authentication (MFA) callbacks, Claims Retrieval, and memory-efficient Document Downloads.
4
+
5
+ ## Installation
6
+
7
+ You can install the library directly from source (or via pip once published):
8
+
9
+ ```bash
10
+ pip install vhi-python
11
+ ```
12
+
13
+ Alternatively, to install it locally for development:
14
+
15
+ ```bash
16
+ git clone https://github.com/agent/vhi-python.git
17
+ cd vhi-python
18
+ pip install -e .
19
+ ```
20
+
21
+ ## Quick Start Example
22
+
23
+ This example demonstrates how to use the library to log in, handle an MFA callback (simulated via CLI input), retrieve claims, and download a claim PDF.
24
+
25
+ ```python
26
+ import os
27
+ from vhi.client import VhiClient
28
+ from vhi.exceptions import VhiAuthenticationError, VhiApiError
29
+
30
+ def my_cli_mfa_callback() -> str:
31
+ """
32
+ A simple callback function that pauses execution
33
+ and waits for the user to input the 2FA code sent to their phone/email.
34
+ """
35
+ return input("Vhi 2FA Code Required. Enter OTP: ").strip()
36
+
37
+ def main():
38
+ # 1. Initialize the client
39
+ # Replace with your actual credentials
40
+ username = os.getenv("VHI_USERNAME", "your_email@example.com")
41
+ password = os.getenv("VHI_PASSWORD", "your_password")
42
+
43
+ # We pass our callback function so the client can automatically
44
+ # pause and invoke it if the server demands MFA.
45
+ client = VhiClient(
46
+ username=username,
47
+ password=password,
48
+ mfa_callback=my_cli_mfa_callback
49
+ )
50
+
51
+ try:
52
+ # 2. Login
53
+ print("Logging in...")
54
+ client.login()
55
+ print("Login successful!")
56
+
57
+ # 3. Retrieve Claims
58
+ print("\nRetrieving claims...")
59
+ statements = client.get_claims()
60
+
61
+ if not statements:
62
+ print("No claim statements found.")
63
+ return
64
+
65
+ for claim in statements:
66
+ print(f"- Claim {claim.claim_id} | Date: {claim.date_of_service} | Status: {claim.status}")
67
+
68
+ # 4. Download a Document
69
+ # We will download the first available document as an example
70
+ first_claim = statements[0]
71
+ if first_claim.document_id:
72
+ download_path = f"{first_claim.claim_id}.pdf"
73
+ print(f"\nDownloading document {first_claim.document_id} to {download_path}...")
74
+ client.download_document(first_claim.document_id, download_path)
75
+ print("Download complete!")
76
+
77
+ except VhiAuthenticationError as e:
78
+ print(f"Authentication failed: {e}")
79
+ except VhiApiError as e:
80
+ print(f"API Error occurred: {e}")
81
+
82
+ if __name__ == "__main__":
83
+ main()
84
+ ```
85
+
86
+ ## Error Handling
87
+
88
+ The library provides structural custom exceptions:
89
+ - `VhiAuthenticationError`: Raised when login fails due to invalid credentials.
90
+ - `VhiMfaRequiredError`: Raised when the server requests MFA but you didn't provide a callback, or when the callback returns an invalid OTP.
91
+ - `VhiApiError`: Raised for any other 4xx or 5xx API errors. Contains standard HTTP status codes for debugging.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vhi-python"
7
+ version = "0.1.0"
8
+ description = "A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Agent", email = "agent@example.com" }
12
+ ]
13
+ license = { text = "MIT" }
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: OS Independent",
18
+ "Intended Audience :: Developers"
19
+ ]
20
+ dependencies = [
21
+ "requests>=2.25.0",
22
+ ]
23
+ requires-python = ">=3.8"
24
+
25
+ [project.optional-dependencies]
26
+ dev = [
27
+ "pytest>=7.0.0",
28
+ "responses>=0.23.0"
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/agent/vhi-python"
33
+ Repository = "https://github.com/agent/vhi-python.git"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from .client import VhiClient
2
+ from .exceptions import VhiAuthenticationError, VhiMfaRequiredError, VhiApiError
3
+ from .models import ClaimStatement
4
+
5
+ __all__ = [
6
+ "VhiClient",
7
+ "VhiAuthenticationError",
8
+ "VhiMfaRequiredError",
9
+ "VhiApiError",
10
+ "ClaimStatement"
11
+ ]
@@ -0,0 +1,199 @@
1
+ import requests
2
+ import os
3
+ from typing import Callable, Optional, List
4
+ from .exceptions import VhiAuthenticationError, VhiMfaRequiredError, VhiApiError
5
+ from .models import ClaimStatement
6
+
7
+ class VhiClient:
8
+ """
9
+ A client library for interacting with the Vhi API.
10
+ Handles authentication, MFA callbacks, claims retrieval, and document downloads.
11
+ """
12
+ DEFAULT_CONFIG_URL = "https://www.vhi.ie/myvhi/myclaimstatements" # Usually embedded here
13
+
14
+ def __init__(self, username: str, password: str, mfa_callback: Optional[Callable[[], str]] = None):
15
+ """
16
+ Initialize the VhiClient.
17
+
18
+ Args:
19
+ username: User's email address.
20
+ password: User's password.
21
+ mfa_callback: A callable that returns the 2FA OTP string when invoked.
22
+ """
23
+ self.username = username
24
+ self.password = password
25
+ self.mfa_callback = mfa_callback
26
+
27
+ # State management
28
+ self.session = requests.Session()
29
+
30
+ # Default Browser Headers to mimic browser and avoid WAF
31
+ self.session.headers.update({
32
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
33
+ "Accept-Language": "en-US,en;q=0.9",
34
+ })
35
+
36
+ self.is_authenticated = False
37
+ self.apis_base_url = "https://apis.vhi.ie" # Will be updated during discovery if necessary
38
+
39
+ def _fetch_environment_config(self):
40
+ """
41
+ Optional: Dynamically discovers the API base URL.
42
+ Currently defaults to known https://apis.vhi.ie.
43
+ """
44
+ try:
45
+ # We skip full parsing of the config to avoid complex scraping
46
+ # We hardcode the reliable apis.vhi.ie gateway for now as per instructions.
47
+ pass
48
+ except Exception:
49
+ pass
50
+
51
+ def login(self):
52
+ """
53
+ Performs the login flow. Handles the MFA challenge if required.
54
+ """
55
+ # Set proper fetch headers
56
+ headers = {
57
+ "Content-Type": "application/json",
58
+ "Accept": "application/json",
59
+ "Origin": "https://www.vhi.ie",
60
+ "Referer": "https://www.vhi.ie/",
61
+ "Sec-Fetch-Mode": "cors",
62
+ "Sec-Fetch-Site": "same-site",
63
+ }
64
+
65
+ payload = {
66
+ "username": self.username,
67
+ "password": self.password
68
+ }
69
+
70
+ # 1. Primary Authentication
71
+ # The path here is a presumed standard, will likely be updated if the actual path varies
72
+ login_url = f"{self.apis_base_url}/auth/v1/login"
73
+
74
+ response = self.session.post(login_url, json=payload, headers=headers)
75
+
76
+ if response.status_code == 200:
77
+ self.is_authenticated = True
78
+ return
79
+
80
+ elif response.status_code == 202:
81
+ # MFA Challenge Required
82
+ try:
83
+ data = response.json()
84
+ except ValueError:
85
+ raise VhiApiError("MFA response was not valid JSON", status_code=response.status_code)
86
+
87
+ if data.get("mfa_required"):
88
+ state_token = data.get("state_token")
89
+ if not state_token:
90
+ raise VhiApiError("MFA required but no state_token provided in response")
91
+
92
+ self._handle_mfa_challenge(state_token, headers)
93
+ else:
94
+ raise VhiApiError("Received 202 but not MFA challenge", status_code=response.status_code, response=response)
95
+
96
+ elif response.status_code == 401:
97
+ raise VhiAuthenticationError("Invalid credentials provided.")
98
+ else:
99
+ raise VhiApiError(f"Login failed with status {response.status_code}", status_code=response.status_code, response=response)
100
+
101
+ def _handle_mfa_challenge(self, state_token: str, base_headers: dict):
102
+ """
103
+ Internal method to submit the MFA code.
104
+ """
105
+ if not self.mfa_callback:
106
+ raise VhiMfaRequiredError("MFA is required but no mfa_callback was provided to VhiClient.")
107
+
108
+ # Pause execution and invoke the user-defined callback to get the OTP
109
+ otp_code = self.mfa_callback()
110
+
111
+ if not otp_code:
112
+ raise VhiAuthenticationError("MFA callback did not return a valid OTP code.")
113
+
114
+ mfa_url = f"{self.apis_base_url}/auth/v1/mfa/verify"
115
+ payload = {
116
+ "otp": otp_code,
117
+ "state_token": state_token
118
+ }
119
+
120
+ response = self.session.post(mfa_url, json=payload, headers=base_headers)
121
+
122
+ if response.status_code == 200:
123
+ self.is_authenticated = True
124
+
125
+ # The session cookie jar has now been populated with the authorized session
126
+ # Or we might need to extract a Bearer token. This implementation relies
127
+ # on cookies being correctly set via Set-Cookie headers by the API.
128
+ else:
129
+ raise VhiAuthenticationError(f"MFA verification failed. Status: {response.status_code}")
130
+
131
+ def get_claims(self) -> List[ClaimStatement]:
132
+ """
133
+ Retrieves the list of claim statements.
134
+
135
+ Returns:
136
+ List of ClaimStatement objects.
137
+ """
138
+ if not self.is_authenticated:
139
+ raise VhiAuthenticationError("Client is not authenticated. Call login() first.")
140
+
141
+ url = f"{self.apis_base_url}/claims/v1/statements"
142
+
143
+ headers = {
144
+ "Accept": "application/json",
145
+ "Origin": "https://www.vhi.ie",
146
+ "Referer": "https://www.vhi.ie/",
147
+ "Sec-Fetch-Mode": "cors",
148
+ "Sec-Fetch-Site": "same-site",
149
+ }
150
+
151
+ response = self.session.get(url, headers=headers)
152
+
153
+ if response.status_code == 200:
154
+ try:
155
+ data = response.json()
156
+ except ValueError:
157
+ raise VhiApiError("Invalid JSON received for claims statements.", status_code=response.status_code)
158
+
159
+ claims_data = data.get("claims", [])
160
+ return [ClaimStatement.from_dict(claim) for claim in claims_data]
161
+ elif response.status_code == 401:
162
+ self.is_authenticated = False
163
+ raise VhiAuthenticationError("Session expired or unauthorized.")
164
+ else:
165
+ raise VhiApiError(f"Failed to fetch claims: {response.status_code}", status_code=response.status_code, response=response)
166
+
167
+ def download_document(self, document_id: str, dest_path: str):
168
+ """
169
+ Streams a claim PDF document to the local disk.
170
+
171
+ Args:
172
+ document_id: The ID of the document to download.
173
+ dest_path: The file path where the PDF should be saved.
174
+ """
175
+ if not self.is_authenticated:
176
+ raise VhiAuthenticationError("Client is not authenticated. Call login() first.")
177
+
178
+ url = f"{self.apis_base_url}/documents/v1/{document_id}/download"
179
+
180
+ headers = {
181
+ "Accept": "application/pdf",
182
+ "Origin": "https://www.vhi.ie",
183
+ "Referer": "https://www.vhi.ie/",
184
+ "Sec-Fetch-Mode": "cors",
185
+ "Sec-Fetch-Site": "same-site",
186
+ }
187
+
188
+ # Use stream=True to prevent loading large PDFs into memory
189
+ with self.session.get(url, headers=headers, stream=True) as response:
190
+ if response.status_code == 200:
191
+ with open(dest_path, 'wb') as f:
192
+ for chunk in response.iter_content(chunk_size=8192):
193
+ if chunk:
194
+ f.write(chunk)
195
+ elif response.status_code == 401:
196
+ self.is_authenticated = False
197
+ raise VhiAuthenticationError("Session expired or unauthorized while downloading document.")
198
+ else:
199
+ raise VhiApiError(f"Failed to download document {document_id}: {response.status_code}", status_code=response.status_code, response=response)
@@ -0,0 +1,18 @@
1
+ class VhiError(Exception):
2
+ """Base exception for Vhi Python Client"""
3
+ pass
4
+
5
+ class VhiAuthenticationError(VhiError):
6
+ """Raised when authentication fails (invalid credentials)"""
7
+ pass
8
+
9
+ class VhiMfaRequiredError(VhiError):
10
+ """Raised when MFA is required but no callback is provided or callback fails"""
11
+ pass
12
+
13
+ class VhiApiError(VhiError):
14
+ """Raised when the Vhi API returns an error response"""
15
+ def __init__(self, message, status_code=None, response=None):
16
+ super().__init__(message)
17
+ self.status_code = status_code
18
+ self.response = response
@@ -0,0 +1,20 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ @dataclass
5
+ class ClaimStatement:
6
+ claim_id: str
7
+ date_of_service: str
8
+ practitioner: str
9
+ document_id: str
10
+ status: str
11
+
12
+ @classmethod
13
+ def from_dict(cls, data: dict) -> "ClaimStatement":
14
+ return cls(
15
+ claim_id=data.get("claimId", ""),
16
+ date_of_service=data.get("dateOfService", ""),
17
+ practitioner=data.get("practitioner", ""),
18
+ document_id=data.get("documentId", ""),
19
+ status=data.get("status", "")
20
+ )
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: vhi-python
3
+ Version: 0.1.0
4
+ Summary: A Python client library for the Vhi API, handling authentication, MFA callbacks, claims retrieval, and document downloads.
5
+ Author-email: Agent <agent@example.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/agent/vhi-python
8
+ Project-URL: Repository, https://github.com/agent/vhi-python.git
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Intended Audience :: Developers
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: requests>=2.25.0
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
18
+ Requires-Dist: responses>=0.23.0; extra == "dev"
19
+
20
+ # Vhi Python Client
21
+
22
+ A Python client library for interacting dynamically with the Vhi API. Provides automated handling of Session state, Multi-Factor Authentication (MFA) callbacks, Claims Retrieval, and memory-efficient Document Downloads.
23
+
24
+ ## Installation
25
+
26
+ You can install the library directly from source (or via pip once published):
27
+
28
+ ```bash
29
+ pip install vhi-python
30
+ ```
31
+
32
+ Alternatively, to install it locally for development:
33
+
34
+ ```bash
35
+ git clone https://github.com/agent/vhi-python.git
36
+ cd vhi-python
37
+ pip install -e .
38
+ ```
39
+
40
+ ## Quick Start Example
41
+
42
+ This example demonstrates how to use the library to log in, handle an MFA callback (simulated via CLI input), retrieve claims, and download a claim PDF.
43
+
44
+ ```python
45
+ import os
46
+ from vhi.client import VhiClient
47
+ from vhi.exceptions import VhiAuthenticationError, VhiApiError
48
+
49
+ def my_cli_mfa_callback() -> str:
50
+ """
51
+ A simple callback function that pauses execution
52
+ and waits for the user to input the 2FA code sent to their phone/email.
53
+ """
54
+ return input("Vhi 2FA Code Required. Enter OTP: ").strip()
55
+
56
+ def main():
57
+ # 1. Initialize the client
58
+ # Replace with your actual credentials
59
+ username = os.getenv("VHI_USERNAME", "your_email@example.com")
60
+ password = os.getenv("VHI_PASSWORD", "your_password")
61
+
62
+ # We pass our callback function so the client can automatically
63
+ # pause and invoke it if the server demands MFA.
64
+ client = VhiClient(
65
+ username=username,
66
+ password=password,
67
+ mfa_callback=my_cli_mfa_callback
68
+ )
69
+
70
+ try:
71
+ # 2. Login
72
+ print("Logging in...")
73
+ client.login()
74
+ print("Login successful!")
75
+
76
+ # 3. Retrieve Claims
77
+ print("\nRetrieving claims...")
78
+ statements = client.get_claims()
79
+
80
+ if not statements:
81
+ print("No claim statements found.")
82
+ return
83
+
84
+ for claim in statements:
85
+ print(f"- Claim {claim.claim_id} | Date: {claim.date_of_service} | Status: {claim.status}")
86
+
87
+ # 4. Download a Document
88
+ # We will download the first available document as an example
89
+ first_claim = statements[0]
90
+ if first_claim.document_id:
91
+ download_path = f"{first_claim.claim_id}.pdf"
92
+ print(f"\nDownloading document {first_claim.document_id} to {download_path}...")
93
+ client.download_document(first_claim.document_id, download_path)
94
+ print("Download complete!")
95
+
96
+ except VhiAuthenticationError as e:
97
+ print(f"Authentication failed: {e}")
98
+ except VhiApiError as e:
99
+ print(f"API Error occurred: {e}")
100
+
101
+ if __name__ == "__main__":
102
+ main()
103
+ ```
104
+
105
+ ## Error Handling
106
+
107
+ The library provides structural custom exceptions:
108
+ - `VhiAuthenticationError`: Raised when login fails due to invalid credentials.
109
+ - `VhiMfaRequiredError`: Raised when the server requests MFA but you didn't provide a callback, or when the callback returns an invalid OTP.
110
+ - `VhiApiError`: Raised for any other 4xx or 5xx API errors. Contains standard HTTP status codes for debugging.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/vhi/__init__.py
4
+ src/vhi/client.py
5
+ src/vhi/exceptions.py
6
+ src/vhi/models.py
7
+ src/vhi_python.egg-info/PKG-INFO
8
+ src/vhi_python.egg-info/SOURCES.txt
9
+ src/vhi_python.egg-info/dependency_links.txt
10
+ src/vhi_python.egg-info/requires.txt
11
+ src/vhi_python.egg-info/top_level.txt
12
+ tests/test_client.py
@@ -0,0 +1,5 @@
1
+ requests>=2.25.0
2
+
3
+ [dev]
4
+ pytest>=7.0.0
5
+ responses>=0.23.0
@@ -0,0 +1,112 @@
1
+ import pytest
2
+ import responses
3
+ import os
4
+ import tempfile
5
+ from vhi.client import VhiClient
6
+ from vhi.exceptions import VhiAuthenticationError, VhiMfaRequiredError, VhiApiError
7
+
8
+ @responses.activate
9
+ def test_login_success_no_mfa():
10
+ client = VhiClient("test@example.com", "password")
11
+
12
+ responses.add(
13
+ responses.POST,
14
+ "https://apis.vhi.ie/auth/v1/login",
15
+ json={"token": "test-token"},
16
+ status=200
17
+ )
18
+
19
+ client.login()
20
+ assert client.is_authenticated is True
21
+
22
+ @responses.activate
23
+ def test_login_requires_mfa_success():
24
+ def mfa_callback():
25
+ return "123456"
26
+
27
+ client = VhiClient("test@example.com", "password", mfa_callback=mfa_callback)
28
+
29
+ # Mock primary login returning 202
30
+ responses.add(
31
+ responses.POST,
32
+ "https://apis.vhi.ie/auth/v1/login",
33
+ json={"mfa_required": True, "state_token": "state-123"},
34
+ status=202
35
+ )
36
+
37
+ # Mock MFA verify returning 200
38
+ responses.add(
39
+ responses.POST,
40
+ "https://apis.vhi.ie/auth/v1/mfa/verify",
41
+ json={"token": "final-token"},
42
+ status=200
43
+ )
44
+
45
+ client.login()
46
+ assert client.is_authenticated is True
47
+
48
+ @responses.activate
49
+ def test_login_invalid_credentials():
50
+ client = VhiClient("test@example.com", "wrong")
51
+
52
+ responses.add(
53
+ responses.POST,
54
+ "https://apis.vhi.ie/auth/v1/login",
55
+ status=401
56
+ )
57
+
58
+ with pytest.raises(VhiAuthenticationError):
59
+ client.login()
60
+
61
+ @responses.activate
62
+ def test_get_claims_success():
63
+ client = VhiClient("test@example.com", "password")
64
+ client.is_authenticated = True # Mock existing session
65
+
66
+ responses.add(
67
+ responses.GET,
68
+ "https://apis.vhi.ie/claims/v1/statements",
69
+ json={
70
+ "claims": [
71
+ {
72
+ "claimId": "CLM-123",
73
+ "dateOfService": "2026-03-15",
74
+ "practitioner": "Dr. Smith",
75
+ "documentId": "DOC-123",
76
+ "status": "Processed"
77
+ }
78
+ ]
79
+ },
80
+ status=200
81
+ )
82
+
83
+ claims = client.get_claims()
84
+ assert len(claims) == 1
85
+ assert claims[0].claim_id == "CLM-123"
86
+ assert claims[0].practitioner == "Dr. Smith"
87
+
88
+ @responses.activate
89
+ def test_download_document_success():
90
+ client = VhiClient("test@example.com", "password")
91
+ client.is_authenticated = True
92
+
93
+ pdf_content = b"%PDF-1.4 mock pdf content"
94
+
95
+ responses.add(
96
+ responses.GET,
97
+ "https://apis.vhi.ie/documents/v1/DOC-123/download",
98
+ body=pdf_content,
99
+ status=200,
100
+ content_type="application/pdf"
101
+ )
102
+
103
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
104
+ dest_path = tmp.name
105
+
106
+ try:
107
+ client.download_document("DOC-123", dest_path)
108
+ with open(dest_path, "rb") as f:
109
+ saved_content = f.read()
110
+ assert saved_content == pdf_content
111
+ finally:
112
+ os.remove(dest_path)