Skip to content

Commit 6f6a9e8

Browse files
authored
[TOOLSLIBS-2506] Account for non str response (#221)
* account for non str responses + tests * release 7.2.1 * test string fix * adds new sms datetime fields * adds new sms field tests * bumps version
1 parent 8e3b7cb commit 6f6a9e8

File tree

5 files changed

+401
-29
lines changed

5 files changed

+401
-29
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# urbanairship changelog
22

3+
## 7.3.0
4+
5+
- Adds new opt_in_date and opt_out_date fields to device listing and lookup for SMS channels.
6+
- Fixes an issue related to attempted datetime conversion from non-string types.
7+
38
## 7.2.0
49

510
- Adds new fields to the SMS class for enhanced opt-in management:

tests/devices/test_device_info.py

Lines changed: 285 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ def test_channel_list(self):
2525
response.status_code = 200
2626
mock_request.return_value = response
2727

28-
url = (
29-
"https://go.urbanairship.com/api/channels/"
30-
"0492662a-1b52-4343-a1f9-c6b0c72931c0"
31-
)
28+
url = "https://go.urbanairship.com/api/channels/0492662a-1b52-4343-a1f9-c6b0c72931c0"
3229

3330
airship = ua.Airship(TEST_KEY, TEST_SECRET)
3431
channel_list = ua.ChannelList(airship, url)
@@ -73,9 +70,7 @@ def test_channel_lookup(self):
7370
channel_id = "0492662a-1b52-4343-a1f9-c6b0c72931c0"
7471
channel_lookup = ua.ChannelInfo(airship).lookup(channel_id)
7572

76-
date_created = datetime.datetime.strptime(
77-
"2014-04-17T23:35:15", "%Y-%m-%dT%H:%M:%S"
78-
)
73+
date_created = datetime.datetime.strptime("2014-04-17T23:35:15", "%Y-%m-%dT%H:%M:%S")
7974

8075
self.assertEqual(channel_lookup.channel_id, channel_id)
8176
self.assertEqual(channel_lookup.device_type, "ios")
@@ -85,9 +80,7 @@ def test_channel_lookup(self):
8580
self.assertEqual(channel_lookup.alias, "null")
8681
self.assertEqual(channel_lookup.tags, ["test_tag"])
8782
self.assertEqual(channel_lookup.created, date_created)
88-
self.assertEqual(
89-
channel_lookup.push_address, "3C0590EBCC11618723B3D4C8AA60BCFB6"
90-
)
83+
self.assertEqual(channel_lookup.push_address, "3C0590EBCC11618723B3D4C8AA60BCFB6")
9184
self.assertEqual(channel_lookup.last_registration, "UNKNOWN")
9285
self.assertEqual(
9386
channel_lookup.ios,
@@ -97,3 +90,285 @@ def test_channel_lookup(self):
9790
"tz": "null",
9891
},
9992
)
93+
94+
def test_channel_info_datetime_parsing_with_none_values(self):
95+
"""Test that ChannelInfo handles None values for datetime fields correctly."""
96+
with mock.patch.object(ua.Airship, "_request") as mock_request:
97+
response = requests.Response()
98+
response._content = json.dumps(
99+
{
100+
"channel": {
101+
"channel_id": "test-channel-id",
102+
"device_type": "ios",
103+
"created": "2023-01-01T12:00:00",
104+
"last_registration": None, # This should not cause a ValueError
105+
"commercial_opted_in": None,
106+
"commercial_opted_out": None,
107+
"transactional_opted_in": None,
108+
"transactional_opted_out": None,
109+
}
110+
}
111+
).encode("utf-8")
112+
response.status_code = 200
113+
mock_request.return_value = response
114+
115+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
116+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
117+
118+
# Valid datetime should be parsed
119+
expected_created = datetime.datetime.strptime(
120+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
121+
)
122+
self.assertEqual(channel_info.created, expected_created)
123+
124+
# None values should remain None (not converted to "UNKNOWN")
125+
self.assertIsNone(channel_info.last_registration)
126+
self.assertIsNone(channel_info.commercial_opted_in)
127+
self.assertIsNone(channel_info.commercial_opted_out)
128+
self.assertIsNone(channel_info.transactional_opted_in)
129+
self.assertIsNone(channel_info.transactional_opted_out)
130+
131+
def test_channel_info_datetime_parsing_with_empty_strings(self):
132+
"""Test that ChannelInfo handles empty strings for datetime fields correctly."""
133+
with mock.patch.object(ua.Airship, "_request") as mock_request:
134+
response = requests.Response()
135+
response._content = json.dumps(
136+
{
137+
"channel": {
138+
"channel_id": "test-channel-id",
139+
"device_type": "ios",
140+
"created": "2023-01-01T12:00:00",
141+
"last_registration": "", # Empty string should not cause ValueError
142+
"commercial_opted_in": "",
143+
}
144+
}
145+
).encode("utf-8")
146+
response.status_code = 200
147+
mock_request.return_value = response
148+
149+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
150+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
151+
152+
# Valid datetime should be parsed
153+
expected_created = datetime.datetime.strptime(
154+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
155+
)
156+
self.assertEqual(channel_info.created, expected_created)
157+
158+
# Empty strings should remain empty strings
159+
self.assertEqual(channel_info.last_registration, "")
160+
self.assertEqual(channel_info.commercial_opted_in, "")
161+
162+
def test_channel_info_datetime_parsing_with_invalid_strings(self):
163+
"""Test that ChannelInfo handles invalid datetime strings correctly."""
164+
with mock.patch.object(ua.Airship, "_request") as mock_request:
165+
response = requests.Response()
166+
response._content = json.dumps(
167+
{
168+
"channel": {
169+
"channel_id": "test-channel-id",
170+
"device_type": "ios",
171+
"created": "2023-01-01T12:00:00",
172+
"last_registration": "invalid-datetime", # Should become "UNKNOWN"
173+
"commercial_opted_in": "not-a-date",
174+
}
175+
}
176+
).encode("utf-8")
177+
response.status_code = 200
178+
mock_request.return_value = response
179+
180+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
181+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
182+
183+
# Valid datetime should be parsed
184+
expected_created = datetime.datetime.strptime(
185+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
186+
)
187+
self.assertEqual(channel_info.created, expected_created)
188+
189+
# Invalid datetime strings should become "UNKNOWN"
190+
self.assertEqual(channel_info.last_registration, "UNKNOWN")
191+
self.assertEqual(channel_info.commercial_opted_in, "UNKNOWN")
192+
193+
def test_device_info_datetime_parsing_with_none_values(self):
194+
"""Test that DeviceInfo handles None values for datetime fields correctly."""
195+
# Test DeviceInfo.from_payload directly
196+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
197+
198+
# Test with None value for created field
199+
payload = {
200+
"device_token": "test-device-token",
201+
"created": None, # This should not cause a ValueError
202+
"active": True,
203+
"tags": ["test_tag"],
204+
}
205+
206+
device_info = ua.DeviceInfo.from_payload(payload, "device_token", airship)
207+
208+
# None value should remain None
209+
self.assertIsNone(device_info.created)
210+
self.assertEqual(device_info.id, "test-device-token")
211+
self.assertEqual(device_info.device_type, "device_token")
212+
self.assertTrue(device_info.active)
213+
self.assertEqual(device_info.tags, ["test_tag"])
214+
215+
def test_device_info_datetime_parsing_with_valid_datetime(self):
216+
"""Test that DeviceInfo correctly parses valid datetime strings."""
217+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
218+
219+
payload = {
220+
"device_token": "test-device-token",
221+
"created": "2023-01-01 12:00:00", # Space-separated format for DeviceInfo
222+
"active": True,
223+
"tags": ["test_tag"],
224+
}
225+
226+
device_info = ua.DeviceInfo.from_payload(payload, "device_token", airship)
227+
228+
# Valid datetime should be parsed
229+
expected_created = datetime.datetime.strptime("2023-01-01 12:00:00", "%Y-%m-%d %H:%M:%S")
230+
self.assertEqual(device_info.created, expected_created)
231+
232+
def test_device_info_datetime_parsing_with_invalid_strings(self):
233+
"""Test that DeviceInfo handles invalid datetime strings correctly."""
234+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
235+
236+
payload = {
237+
"device_token": "test-device-token",
238+
"created": "invalid-datetime", # Should become "UNKNOWN"
239+
"active": True,
240+
"tags": ["test_tag"],
241+
}
242+
243+
device_info = ua.DeviceInfo.from_payload(payload, "device_token", airship)
244+
245+
# Invalid datetime string should become "UNKNOWN"
246+
self.assertEqual(device_info.created, "UNKNOWN")
247+
248+
def test_channel_info_opt_in_date_opt_out_date_with_none_values(self):
249+
"""Test that ChannelInfo handles None values for opt_in_date and opt_out_date correctly."""
250+
with mock.patch.object(ua.Airship, "_request") as mock_request:
251+
response = requests.Response()
252+
response._content = json.dumps(
253+
{
254+
"channel": {
255+
"channel_id": "test-channel-id",
256+
"device_type": "ios",
257+
"created": "2023-01-01T12:00:00",
258+
"opt_in_date": None, # This should not cause a ValueError
259+
"opt_out_date": None, # This should not cause a ValueError
260+
}
261+
}
262+
).encode("utf-8")
263+
response.status_code = 200
264+
mock_request.return_value = response
265+
266+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
267+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
268+
269+
# Valid datetime should be parsed
270+
expected_created = datetime.datetime.strptime(
271+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
272+
)
273+
self.assertEqual(channel_info.created, expected_created)
274+
275+
# None values should remain None (not converted to "UNKNOWN")
276+
self.assertIsNone(channel_info.opt_in_date)
277+
self.assertIsNone(channel_info.opt_out_date)
278+
279+
def test_channel_info_opt_in_date_opt_out_date_with_valid_datetime(self):
280+
"""Test that ChannelInfo correctly parses valid opt_in_date and opt_out_date datetime strings."""
281+
with mock.patch.object(ua.Airship, "_request") as mock_request:
282+
response = requests.Response()
283+
response._content = json.dumps(
284+
{
285+
"channel": {
286+
"channel_id": "test-channel-id",
287+
"device_type": "ios",
288+
"created": "2023-01-01T12:00:00",
289+
"opt_in_date": "2023-01-15T10:30:00",
290+
"opt_out_date": "2023-02-20T14:45:00",
291+
}
292+
}
293+
).encode("utf-8")
294+
response.status_code = 200
295+
mock_request.return_value = response
296+
297+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
298+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
299+
300+
# Valid datetime should be parsed
301+
expected_created = datetime.datetime.strptime(
302+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
303+
)
304+
expected_opt_in_date = datetime.datetime.strptime(
305+
"2023-01-15T10:30:00", "%Y-%m-%dT%H:%M:%S"
306+
)
307+
expected_opt_out_date = datetime.datetime.strptime(
308+
"2023-02-20T14:45:00", "%Y-%m-%dT%H:%M:%S"
309+
)
310+
self.assertEqual(channel_info.created, expected_created)
311+
self.assertEqual(channel_info.opt_in_date, expected_opt_in_date)
312+
self.assertEqual(channel_info.opt_out_date, expected_opt_out_date)
313+
314+
def test_channel_info_opt_in_date_opt_out_date_with_empty_strings(self):
315+
"""Test that ChannelInfo handles empty strings for opt_in_date and opt_out_date correctly."""
316+
with mock.patch.object(ua.Airship, "_request") as mock_request:
317+
response = requests.Response()
318+
response._content = json.dumps(
319+
{
320+
"channel": {
321+
"channel_id": "test-channel-id",
322+
"device_type": "ios",
323+
"created": "2023-01-01T12:00:00",
324+
"opt_in_date": "", # Empty string should not cause ValueError
325+
"opt_out_date": "", # Empty string should not cause ValueError
326+
}
327+
}
328+
).encode("utf-8")
329+
response.status_code = 200
330+
mock_request.return_value = response
331+
332+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
333+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
334+
335+
# Valid datetime should be parsed
336+
expected_created = datetime.datetime.strptime(
337+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
338+
)
339+
self.assertEqual(channel_info.created, expected_created)
340+
341+
# Empty strings should remain empty strings
342+
self.assertEqual(channel_info.opt_in_date, "")
343+
self.assertEqual(channel_info.opt_out_date, "")
344+
345+
def test_channel_info_opt_in_date_opt_out_date_with_invalid_strings(self):
346+
"""Test that ChannelInfo handles invalid opt_in_date and opt_out_date datetime strings correctly."""
347+
with mock.patch.object(ua.Airship, "_request") as mock_request:
348+
response = requests.Response()
349+
response._content = json.dumps(
350+
{
351+
"channel": {
352+
"channel_id": "test-channel-id",
353+
"device_type": "ios",
354+
"created": "2023-01-01T12:00:00",
355+
"opt_in_date": "invalid-datetime", # Should become "UNKNOWN"
356+
"opt_out_date": "not-a-date", # Should become "UNKNOWN"
357+
}
358+
}
359+
).encode("utf-8")
360+
response.status_code = 200
361+
mock_request.return_value = response
362+
363+
airship = ua.Airship(TEST_KEY, TEST_SECRET)
364+
channel_info = ua.ChannelInfo(airship).lookup("test-channel-id")
365+
366+
# Valid datetime should be parsed
367+
expected_created = datetime.datetime.strptime(
368+
"2023-01-01T12:00:00", "%Y-%m-%dT%H:%M:%S"
369+
)
370+
self.assertEqual(channel_info.created, expected_created)
371+
372+
# Invalid datetime strings should become "UNKNOWN"
373+
self.assertEqual(channel_info.opt_in_date, "UNKNOWN")
374+
self.assertEqual(channel_info.opt_out_date, "UNKNOWN")

0 commit comments

Comments
 (0)