Skip to content

Commit c434fb0

Browse files
author
Nick Bragdon
authored
Merge pull request #1 from CMSgov/BB2-1117_fhir_endpoints
[ BB2-1117 ] fhir endpoints
2 parents 85ff42e + 6b95d96 commit c434fb0

4 files changed

Lines changed: 149 additions & 0 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,12 @@ dmypy.json
148148
# Cython debug symbols
149149
cython_debug/
150150

151+
# VS Code
152+
.vscode/
153+
154+
# Virtual Env
155+
bb2_env/
156+
151157
# PyCharm
152158
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
153159
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore

src/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SDK_HEADER = "python"
2+
SDK_HEADER_KEY = "X-BLUEBUTTON-SDK"
3+
REFRESH_TOKEN_ENDPOINT = "/o/token/"

src/fhir_request.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import datetime
2+
from constants import REFRESH_TOKEN_ENDPOINT, SDK_HEADER, SDK_HEADER_KEY
3+
import requests
4+
from requests.adapters import HTTPAdapter, Retry
5+
6+
7+
def fhir_request(bb, config):
8+
auth_token = config["auth_token"]
9+
new_auth_token = handle_expired(bb, auth_token)
10+
11+
if new_auth_token is not None:
12+
auth_token = new_auth_token
13+
14+
retry_config = Retry(
15+
total=3, backoff_factor=5, status_forcelist=[500, 502, 503, 504]
16+
)
17+
full_url = bb["base_url"] + "/v" + bb["version"] + config["url"]
18+
headers = {"Authorization": "Bearer " + auth_token["access_token"]}
19+
headers[SDK_HEADER_KEY] = SDK_HEADER
20+
adapter = HTTPAdapter(max_retries=retry_config)
21+
sesh = requests.Session()
22+
sesh.mount("https://", adapter)
23+
sesh.mount("http://", adapter)
24+
response = sesh.get(url=full_url, params=config["params"], headers=headers)
25+
26+
return {"auth_token": new_auth_token, "response": response}
27+
28+
29+
def handle_expired(bb, auth_token):
30+
if datetime.datetime.now() > auth_token["expires_at"]:
31+
return refresh_access_token(bb, auth_token["refresh_token"])
32+
33+
return None
34+
35+
36+
def refresh_access_token(bb, refresh_token):
37+
full_url = bb["base_url"] + "/v" + bb["version"] + REFRESH_TOKEN_ENDPOINT
38+
39+
params = {
40+
"client_id": bb["client_id"],
41+
"grant_type": "refresh_token",
42+
"refresh_token": refresh_token,
43+
}
44+
45+
my_response = requests.post(
46+
url=full_url, params=params, auth=(bb["client_id"], bb["client_secret"])
47+
)
48+
response_json = my_response.json()
49+
response_json["expires_at"] = datetime.datetime.now() + datetime.timedelta(
50+
seconds=response_json["expires_in"]
51+
)
52+
return response_json

src/test_fhir_request.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import datetime
2+
import unittest
3+
from unittest import mock
4+
from fhirRequest import fhirRequest
5+
6+
7+
class MockResponse:
8+
def __init__(self, json_data, status_code):
9+
self.json_data = json_data
10+
self.status_code = status_code
11+
12+
def json(self):
13+
return self.json_data
14+
15+
16+
class MockSession:
17+
def __init__(self, json_data, status_code):
18+
self.response = MockResponse(json_data, status_code)
19+
20+
def mount(self, *args, **kwargs):
21+
return
22+
23+
def get(self, *args, **kwargs):
24+
return self.response
25+
26+
27+
def success_fhir_request_mock(*args, **kwargs):
28+
return MockSession({"resourceType": "Patient", "id": "-20140000010000"}, 200)
29+
30+
31+
def error_fhir_request_mock(*args, **kwargs):
32+
return MockSession(None, 500)
33+
34+
35+
def not_found_fhir_request_mock(*args, **kwargs):
36+
return MockSession(None, 404)
37+
38+
39+
def generate_mock_bb():
40+
return {
41+
"base_url": "https://sandbox.bluebutton.cms.gov",
42+
"client_id": "fake_client_id",
43+
"client_secret": "fake_client_secret",
44+
"version": "2",
45+
}
46+
47+
48+
def generate_mock_config():
49+
return {
50+
"url": "/fhir/Patient/-20140000010000",
51+
"params": {},
52+
"auth_token": {
53+
"access_token": "fake_access_token",
54+
"refresh_token": "fake_refresh_token",
55+
"expires_at": datetime.datetime.now() + datetime.timedelta(seconds=36000),
56+
"patient": "-20140000010000",
57+
},
58+
}
59+
60+
61+
class TestAPI(unittest.TestCase):
62+
@mock.patch("requests.Session", side_effect=success_fhir_request_mock)
63+
def test_successful_fhir_request(self, get_request_mock):
64+
bb = generate_mock_bb()
65+
config = generate_mock_config()
66+
response = fhirRequest(bb, config)
67+
self.assertEqual(response["auth_token"], None)
68+
self.assertEqual(response["response"].status_code, 200)
69+
self.assertEqual(response["response"].json()["id"], "-20140000010000")
70+
self.assertEqual(get_request_mock.call_count, 1)
71+
72+
@mock.patch("requests.Session", side_effect=error_fhir_request_mock)
73+
def test_500_error_fhir_request(self, get_request_mock):
74+
bb = generate_mock_bb()
75+
config = generate_mock_config()
76+
response = fhirRequest(bb, config)
77+
self.assertEqual(response["auth_token"], None)
78+
self.assertEqual(response["response"].status_code, 500)
79+
self.assertEqual(get_request_mock.call_count, 1)
80+
81+
@mock.patch("requests.Session", side_effect=not_found_fhir_request_mock)
82+
def test_not_found_fhir_request(self, get_request_mock):
83+
bb = generate_mock_bb()
84+
config = generate_mock_config()
85+
response = fhirRequest(bb, config)
86+
self.assertEqual(response["auth_token"], None)
87+
self.assertEqual(response["response"].status_code, 404)
88+
self.assertEqual(get_request_mock.call_count, 1)

0 commit comments

Comments
 (0)