Skip to content

Commit 21bdc2d

Browse files
authored
Merge pull request #16 from notificationapi-com/lKewaViV/2195-inapp-update-and-pref-libraries
add update_in_app_notification
2 parents 8afb491 + fe34aa2 commit 21bdc2d

File tree

3 files changed

+247
-1
lines changed

3 files changed

+247
-1
lines changed

notificationapi_python_server_sdk/notificationapi.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def init(client_id, client_secret):
2121
__client_secret = client_secret
2222

2323

24-
async def request(method, uri, data=None, custom_auth=None):
24+
async def request(method, uri, data=None, custom_auth=None, queryStrings=None):
2525
api_url = "https://api.notificationapi.com/" + __client_id + "/" + uri
2626

2727
headers = {}
@@ -34,6 +34,7 @@ async def request(method, uri, data=None, custom_auth=None):
3434
response = await client.request(
3535
method,
3636
api_url,
37+
params=queryStrings,
3738
headers=headers,
3839
json=data,
3940
)
@@ -97,6 +98,17 @@ async def set_user_preferences(params):
9798
)
9899

99100

101+
async def delete_user_preferences(params):
102+
user_id = params.pop('id')
103+
104+
hashed_user_id = hashlib.sha256((__client_secret + user_id).encode()).digest()
105+
hashed_user_id_base64 = base64.b64encode(hashed_user_id).decode()
106+
107+
custom_auth = 'Basic ' + base64.b64encode(f'{__client_id}:{user_id}:{hashed_user_id_base64}'.encode()).decode()
108+
109+
await request('DELETE', f'users/{user_id}/preferences', None, custom_auth, params)
110+
111+
100112
async def identify_user(params):
101113
user_id = params.pop('id')
102114

@@ -111,3 +123,11 @@ async def identify_user(params):
111123
async def query_logs(params):
112124
response = await request("POST", "logs/query", params)
113125
return response
126+
127+
128+
async def update_in_app_notification(user_id, params):
129+
hashed_user_id = hashlib.sha256((__client_secret + user_id).encode()).digest()
130+
hashed_user_id_base64 = base64.b64encode(hashed_user_id).decode()
131+
custom_auth = 'Basic ' + base64.b64encode(f'{__client_id}:{user_id}:{hashed_user_id_base64}'.encode()).decode()
132+
133+
return await request('PATCH', f'users/{user_id}/notifications/INAPP_WEB', params, custom_auth)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env python
2+
3+
"""Tests for `notificationapi_python_server_sdk` package."""
4+
5+
import pytest
6+
import urllib.parse
7+
import hashlib
8+
import base64
9+
from httpx import Response
10+
from notificationapi_python_server_sdk import notificationapi
11+
12+
client_id = "client_id"
13+
client_secret = "client_secret"
14+
user_id = "userId"
15+
notification_id = "notification_id"
16+
17+
api_paths = {
18+
"delete_user_preferences":
19+
f"https://api.notificationapi.com/{client_id}/users/{urllib.parse.quote(user_id)}/preferences?notificationId={notification_id}",
20+
}
21+
22+
delete_user_preferences_params = {
23+
"id": user_id,
24+
"notificationId": notification_id
25+
}
26+
27+
28+
@pytest.mark.asyncio
29+
@pytest.mark.parametrize(
30+
"func,params",
31+
[
32+
("delete_user_preferences", delete_user_preferences_params),
33+
],
34+
)
35+
async def test_makes_one_delete_api_call(respx_mock, func, params):
36+
route = respx_mock.delete(api_paths[func]).mock(return_value=Response(200))
37+
notificationapi.init(client_id, client_secret)
38+
await getattr(notificationapi, func)(params)
39+
assert route.called
40+
41+
42+
delete_user_preferences_params = {
43+
"id": user_id,
44+
"notificationId": notification_id
45+
}
46+
@pytest.mark.asyncio
47+
@pytest.mark.parametrize(
48+
"func,params",
49+
[
50+
("delete_user_preferences", delete_user_preferences_params),
51+
],
52+
)
53+
async def test_uses_custom_authorization(respx_mock, func, params):
54+
route = respx_mock.delete(api_paths[func]).mock(return_value=Response(200))
55+
hashed_user_id = hashlib.sha256((client_secret + user_id).encode()).digest()
56+
hashed_user_id_base64 = base64.b64encode(hashed_user_id).decode()
57+
58+
# Create custom authorization header
59+
custom_auth = 'Basic ' + base64.b64encode(f'{client_id}:{user_id}:{hashed_user_id_base64}'.encode()).decode()
60+
notificationapi.init(client_id, client_secret)
61+
await getattr(notificationapi, func)(params)
62+
assert "Authorization" in route.calls.last.request.headers
63+
assert route.calls.last.request.headers["Authorization"] == custom_auth
64+
65+
66+
delete_user_preferences_params = {
67+
"id": user_id,
68+
"notificationId": notification_id
69+
}
70+
71+
72+
@pytest.mark.asyncio
73+
@pytest.mark.parametrize(
74+
"func,params",
75+
[
76+
("delete_user_preferences", delete_user_preferences_params),
77+
],
78+
)
79+
async def test_logs_and_throws_on_500(respx_mock, caplog, func, params):
80+
respx_mock.delete(api_paths[func]).mock(return_value=Response(500, text="big oof 500"))
81+
notificationapi.init(client_id, client_secret)
82+
await getattr(notificationapi, func)(params)
83+
assert "NotificationAPI request failed. Response: big oof 500" in caplog.text
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env python
2+
3+
"""Tests for `notificationapi_python_server_sdk` package."""
4+
5+
import pytest
6+
import hashlib
7+
import base64
8+
import urllib.parse
9+
import json
10+
from httpx import Response
11+
from notificationapi_python_server_sdk import notificationapi
12+
13+
client_id = "client_id"
14+
client_secret = "client_secret"
15+
user_id = "userId"
16+
17+
api_paths = {
18+
"update_in_app_notification": f"https://api.notificationapi.com/{client_id}/users/{urllib.parse.quote(user_id)}/notifications/INAPP_WEB",
19+
}
20+
21+
22+
@pytest.mark.asyncio
23+
@pytest.mark.parametrize(
24+
"func,params",
25+
[
26+
(
27+
"update_in_app_notification",
28+
(
29+
user_id,
30+
{
31+
"trackingIds": ["sampleTrackingId"],
32+
"opened": "1970-01-01T00:00:00.000Z",
33+
"clicked": "1970-01-01T00:00:00.000Z",
34+
"archived": "1970-01-01T00:00:00.000Z",
35+
"actioned1": "1970-01-01T00:00:00.000Z",
36+
"actioned2": "1970-01-01T00:00:00.000Z",
37+
"reply": {"date": "1970-01-01T00:00:00.000Z", "message": "nice!"}
38+
}
39+
),
40+
),
41+
],
42+
)
43+
async def test_makes_one_patch_api_call(respx_mock, func, params):
44+
route = respx_mock.patch(api_paths[func]).mock(return_value=Response(200))
45+
notificationapi.init(client_id, client_secret)
46+
await getattr(notificationapi, func)(*params)
47+
assert route.called
48+
49+
50+
@pytest.mark.parametrize(
51+
"func,params",
52+
[
53+
(
54+
"update_in_app_notification",
55+
(
56+
user_id,
57+
{
58+
"trackingIds": ["sampleTrackingId"],
59+
"opened": "1970-01-01T00:00:00.000Z",
60+
"clicked": "1970-01-01T00:00:00.000Z",
61+
"archived": "1970-01-01T00:00:00.000Z",
62+
"actioned1": "1970-01-01T00:00:00.000Z",
63+
"actioned2": "1970-01-01T00:00:00.000Z",
64+
"reply": {"date": "1970-01-01T00:00:00.000Z", "message": "nice!"}
65+
}
66+
),
67+
),
68+
],
69+
)
70+
async def test_uses_custom_authorization(respx_mock, func, params):
71+
route = respx_mock.patch(api_paths[func]).mock(return_value=Response(200))
72+
hashed_user_id = hashlib.sha256((client_secret + user_id).encode()).digest()
73+
hashed_user_id_base64 = base64.b64encode(hashed_user_id).decode()
74+
75+
# Create custom authorization header
76+
custom_auth = 'Basic ' + base64.b64encode(f'{client_id}:{user_id}:{hashed_user_id_base64}'.encode()).decode()
77+
notificationapi.init(client_id, client_secret)
78+
await getattr(notificationapi, func)(*params)
79+
assert "Authorization" in route.calls.last.request.headers
80+
assert route.calls.last.request.headers["Authorization"] == custom_auth
81+
82+
83+
@pytest.mark.parametrize(
84+
"func,params",
85+
[
86+
(
87+
"update_in_app_notification",
88+
(
89+
user_id,
90+
{
91+
"trackingIds": ["sampleTrackingId"],
92+
"opened": "1970-01-01T00:00:00.000Z",
93+
"clicked": "1970-01-01T00:00:00.000Z",
94+
"archived": "1970-01-01T00:00:00.000Z",
95+
"actioned1": "1970-01-01T00:00:00.000Z",
96+
"actioned2": "1970-01-01T00:00:00.000Z",
97+
"reply": {"date": "1970-01-01T00:00:00.000Z", "message": "nice!"}
98+
}
99+
),
100+
),
101+
],
102+
)
103+
async def test_passes_data_as_json_body(respx_mock, func, params):
104+
route = respx_mock.patch(api_paths[func]).mock(return_value=Response(200))
105+
notificationapi.init(client_id, client_secret)
106+
await getattr(notificationapi, func)(*params)
107+
sent_data = json.loads(route.calls.last.request.content)
108+
assert sent_data == {
109+
"trackingIds": ["sampleTrackingId"],
110+
"opened": "1970-01-01T00:00:00.000Z",
111+
"clicked": "1970-01-01T00:00:00.000Z",
112+
"archived": "1970-01-01T00:00:00.000Z",
113+
"actioned1": "1970-01-01T00:00:00.000Z",
114+
"actioned2": "1970-01-01T00:00:00.000Z",
115+
"reply": {"date": "1970-01-01T00:00:00.000Z", "message": "nice!"}
116+
}
117+
118+
119+
@pytest.mark.parametrize(
120+
"func,params",
121+
[
122+
(
123+
"update_in_app_notification",
124+
(
125+
user_id,
126+
{
127+
"trackingIds": ["sampleTrackingId"],
128+
"opened": "1970-01-01T00:00:00.000Z",
129+
"clicked": "1970-01-01T00:00:00.000Z",
130+
"archived": "1970-01-01T00:00:00.000Z",
131+
"actioned1": "1970-01-01T00:00:00.000Z",
132+
"actioned2": "1970-01-01T00:00:00.000Z",
133+
"reply": {"date": "1970-01-01T00:00:00.000Z", "message": "nice!"}
134+
}
135+
),
136+
),
137+
],
138+
)
139+
async def test_logs_and_throws_on_500(respx_mock, caplog, func, params):
140+
respx_mock.patch(api_paths[func]).mock(return_value=Response(500, text="big oof 500"))
141+
notificationapi.init(client_id, client_secret)
142+
await getattr(notificationapi, func)(*params)
143+
assert "NotificationAPI request failed. Response: big oof 500" in caplog.text

0 commit comments

Comments
 (0)