devcycle-python-server-sdk 2.0.1__py3-none-any.whl → 3.0.0__py3-none-any.whl
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.
- devcycle_python_sdk/VERSION.txt +1 -1
- devcycle_python_sdk/__init__.py +3 -3
- devcycle_python_sdk/api/backoff.py +11 -0
- devcycle_python_sdk/api/bucketing_client.py +17 -25
- devcycle_python_sdk/api/config_client.py +116 -0
- devcycle_python_sdk/api/event_client.py +95 -0
- devcycle_python_sdk/api/local_bucketing.py +429 -2
- devcycle_python_sdk/{dvc_cloud_client.py → cloud_client.py} +69 -41
- devcycle_python_sdk/exceptions.py +25 -3
- devcycle_python_sdk/local_client.py +220 -0
- devcycle_python_sdk/managers/config_manager.py +83 -15
- devcycle_python_sdk/managers/event_queue_manager.py +213 -8
- devcycle_python_sdk/models/bucketed_config.py +150 -0
- devcycle_python_sdk/models/event.py +111 -8
- devcycle_python_sdk/models/feature.py +12 -0
- devcycle_python_sdk/models/platform_data.py +33 -0
- devcycle_python_sdk/models/user.py +52 -7
- devcycle_python_sdk/models/variable.py +17 -5
- devcycle_python_sdk/options.py +106 -0
- devcycle_python_sdk/protobuf/__init__.py +0 -0
- devcycle_python_sdk/protobuf/utils.py +129 -0
- devcycle_python_sdk/protobuf/variableForUserParams_pb2.py +46 -0
- devcycle_python_sdk/util/version.py +1 -1
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/METADATA +3 -1
- devcycle_python_server_sdk-3.0.0.dist-info/RECORD +55 -0
- example/{example.py → cloud_client_example.py} +24 -20
- example/local_bucketing_client_example.py +74 -0
- test/api/test_bucketing_client.py +10 -5
- test/api/test_config_client.py +133 -0
- test/api/test_event_client.py +97 -0
- test/api/test_local_bucketing.py +379 -4
- test/fixture/__init__.py +0 -0
- test/fixture/data.py +57 -0
- test/managers/test_config_manager.py +120 -5
- test/managers/test_event_queue_manager.py +259 -5
- test/models/__init__.py +0 -0
- test/models/test_bucketed_config.py +26 -0
- test/{test_dvc_cloud_client.py → test_cloud_client.py} +11 -17
- test/test_local_client.py +397 -0
- test/util/test_utils.py +183 -0
- test/util/test_version.py +2 -2
- devcycle_python_sdk/dvc_local_client.py +0 -152
- devcycle_python_sdk/dvc_options.py +0 -45
- devcycle_python_server_sdk-2.0.1.dist-info/RECORD +0 -41
- test/test_bucketing_client.py +0 -228
- test/test_dvc_local_client.py +0 -143
- test/test_version.py +0 -18
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/LICENSE +0 -0
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/WHEEL +0 -0
- {devcycle_python_server_sdk-2.0.1.dist-info → devcycle_python_server_sdk-3.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import unittest
|
|
4
|
+
import uuid
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import responses
|
|
8
|
+
|
|
9
|
+
from devcycle_python_sdk import DevCycleLocalClient, DevCycleLocalOptions
|
|
10
|
+
from devcycle_python_sdk.local_client import _validate_user, _validate_sdk_key
|
|
11
|
+
from devcycle_python_sdk.exceptions import MalformedConfigError
|
|
12
|
+
from devcycle_python_sdk.models.event import DevCycleEvent
|
|
13
|
+
from devcycle_python_sdk.models.feature import Feature
|
|
14
|
+
from devcycle_python_sdk.api.local_bucketing import LocalBucketing
|
|
15
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
16
|
+
from devcycle_python_sdk.models.variable import Variable, TypeEnum
|
|
17
|
+
from test.fixture.data import small_config_json
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DevCycleLocalClientTest(unittest.TestCase):
|
|
23
|
+
def setUp(self) -> None:
|
|
24
|
+
self.sdk_key = "dvc_server_949e4962-c624-4d20-a1ea-7f2501b2ba79"
|
|
25
|
+
self.test_config_json = small_config_json()
|
|
26
|
+
self.test_etag = "2f71454e-3279-4ca7-a8e7-802ce97bef43"
|
|
27
|
+
|
|
28
|
+
config_url = "http://localhost/config/v1/server/" + self.sdk_key + ".json"
|
|
29
|
+
|
|
30
|
+
responses.add(
|
|
31
|
+
responses.GET,
|
|
32
|
+
config_url,
|
|
33
|
+
headers={"ETag": self.test_etag},
|
|
34
|
+
json=self.test_config_json,
|
|
35
|
+
status=200,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
self.options = DevCycleLocalOptions(
|
|
39
|
+
config_polling_interval_ms=5000,
|
|
40
|
+
config_cdn_uri="http://localhost/",
|
|
41
|
+
disable_custom_event_logging=True,
|
|
42
|
+
disable_automatic_event_logging=True,
|
|
43
|
+
)
|
|
44
|
+
self.test_user = DevCycleUser(user_id="test_user_id")
|
|
45
|
+
self.test_user_empty_id = DevCycleUser(user_id="")
|
|
46
|
+
self.client = None
|
|
47
|
+
|
|
48
|
+
def tearDown(self) -> None:
|
|
49
|
+
if self.client:
|
|
50
|
+
self.client.close()
|
|
51
|
+
responses.reset()
|
|
52
|
+
|
|
53
|
+
def setup_client(self):
|
|
54
|
+
self.client = DevCycleLocalClient(self.sdk_key, self.options)
|
|
55
|
+
while not self.client.is_initialized():
|
|
56
|
+
time.sleep(0.05)
|
|
57
|
+
|
|
58
|
+
def test_validate_sdk_key(self):
|
|
59
|
+
with self.assertRaises(ValueError):
|
|
60
|
+
_validate_sdk_key(None)
|
|
61
|
+
|
|
62
|
+
with self.assertRaises(ValueError):
|
|
63
|
+
_validate_sdk_key("")
|
|
64
|
+
|
|
65
|
+
with self.assertRaises(ValueError):
|
|
66
|
+
_validate_sdk_key("client_" + str(uuid.uuid4()))
|
|
67
|
+
|
|
68
|
+
with self.assertRaises(ValueError):
|
|
69
|
+
_validate_sdk_key(str(uuid.uuid4()))
|
|
70
|
+
|
|
71
|
+
def test_validate_user(self):
|
|
72
|
+
with self.assertRaises(ValueError):
|
|
73
|
+
_validate_user(None)
|
|
74
|
+
|
|
75
|
+
with self.assertRaises(ValueError):
|
|
76
|
+
_validate_user(self.test_user_empty_id)
|
|
77
|
+
|
|
78
|
+
@responses.activate
|
|
79
|
+
def test_create_client_invalid_sdk_key(self):
|
|
80
|
+
with self.assertRaises(ValueError):
|
|
81
|
+
DevCycleLocalClient(None, None)
|
|
82
|
+
|
|
83
|
+
with self.assertRaises(ValueError):
|
|
84
|
+
DevCycleLocalClient("", None)
|
|
85
|
+
|
|
86
|
+
with self.assertRaises(ValueError):
|
|
87
|
+
DevCycleLocalClient("no prefix in key", None)
|
|
88
|
+
|
|
89
|
+
@responses.activate
|
|
90
|
+
def test_variable_bad_user(self):
|
|
91
|
+
self.setup_client()
|
|
92
|
+
|
|
93
|
+
with self.assertRaises(ValueError):
|
|
94
|
+
self.client.variable(None, "strKey", "default_value")
|
|
95
|
+
|
|
96
|
+
with self.assertRaises(ValueError):
|
|
97
|
+
self.client.variable(self.test_user_empty_id, "strKey", "default_value")
|
|
98
|
+
|
|
99
|
+
@responses.activate
|
|
100
|
+
def test_variable_bad_key_and_value(self):
|
|
101
|
+
self.setup_client()
|
|
102
|
+
|
|
103
|
+
with self.assertRaises(ValueError):
|
|
104
|
+
self.client.variable(self.test_user, None, "default_value")
|
|
105
|
+
|
|
106
|
+
with self.assertRaises(ValueError):
|
|
107
|
+
self.client.variable(self.test_user, "", "default_value")
|
|
108
|
+
|
|
109
|
+
with self.assertRaises(ValueError):
|
|
110
|
+
self.client.variable(self.test_user, "strKey", None)
|
|
111
|
+
|
|
112
|
+
@responses.activate
|
|
113
|
+
def test_all_variables_bad_user(self):
|
|
114
|
+
self.setup_client()
|
|
115
|
+
|
|
116
|
+
with self.assertRaises(ValueError):
|
|
117
|
+
self.client.all_variables(None)
|
|
118
|
+
|
|
119
|
+
with self.assertRaises(ValueError):
|
|
120
|
+
self.client.all_variables(self.test_user_empty_id)
|
|
121
|
+
|
|
122
|
+
@responses.activate
|
|
123
|
+
def test_all_features_bad_user(self):
|
|
124
|
+
self.setup_client()
|
|
125
|
+
|
|
126
|
+
with self.assertRaises(ValueError):
|
|
127
|
+
self.client.all_features(None)
|
|
128
|
+
|
|
129
|
+
with self.assertRaises(ValueError):
|
|
130
|
+
self.client.all_features(self.test_user_empty_id)
|
|
131
|
+
|
|
132
|
+
@responses.activate
|
|
133
|
+
def test_track_event_bad_user(self):
|
|
134
|
+
self.setup_client()
|
|
135
|
+
|
|
136
|
+
event = DevCycleEvent(
|
|
137
|
+
type="user",
|
|
138
|
+
target="test_target",
|
|
139
|
+
value=42,
|
|
140
|
+
metaData={"key": "value"},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
with self.assertRaises(ValueError):
|
|
144
|
+
self.client.track(None, event)
|
|
145
|
+
|
|
146
|
+
with self.assertRaises(ValueError):
|
|
147
|
+
self.client.track(self.test_user_empty_id, event)
|
|
148
|
+
|
|
149
|
+
@responses.activate
|
|
150
|
+
def test_track_bad_event(self):
|
|
151
|
+
self.setup_client()
|
|
152
|
+
|
|
153
|
+
with self.assertRaises(ValueError):
|
|
154
|
+
self.client.track(self.test_user, None)
|
|
155
|
+
|
|
156
|
+
event = DevCycleEvent(
|
|
157
|
+
type=None,
|
|
158
|
+
target="test_target",
|
|
159
|
+
value=42,
|
|
160
|
+
metaData={"key": "value"},
|
|
161
|
+
)
|
|
162
|
+
with self.assertRaises(ValueError):
|
|
163
|
+
self.client.track(self.test_user, event)
|
|
164
|
+
|
|
165
|
+
event = DevCycleEvent(
|
|
166
|
+
type="",
|
|
167
|
+
target="test_target",
|
|
168
|
+
value=42,
|
|
169
|
+
metaData={"key": "value"},
|
|
170
|
+
)
|
|
171
|
+
with self.assertRaises(ValueError):
|
|
172
|
+
self.client.track(self.test_user, event)
|
|
173
|
+
|
|
174
|
+
@responses.activate
|
|
175
|
+
def test_set_client_custom_data(self):
|
|
176
|
+
self.setup_client()
|
|
177
|
+
|
|
178
|
+
# set the data without error
|
|
179
|
+
client_custom_data = {
|
|
180
|
+
"strProp": "strVal",
|
|
181
|
+
"intProp": 1,
|
|
182
|
+
"floatProp": 1.1,
|
|
183
|
+
"boolProp": True,
|
|
184
|
+
"nullProp": None,
|
|
185
|
+
}
|
|
186
|
+
self.client.set_client_custom_data(client_custom_data)
|
|
187
|
+
|
|
188
|
+
@responses.activate
|
|
189
|
+
def test_variable_default(self):
|
|
190
|
+
self.setup_client()
|
|
191
|
+
|
|
192
|
+
# try each default value data type and make sure it gets returned
|
|
193
|
+
tests = [
|
|
194
|
+
("default_value", TypeEnum.STRING),
|
|
195
|
+
(True, TypeEnum.BOOLEAN),
|
|
196
|
+
(1000, TypeEnum.NUMBER),
|
|
197
|
+
(0.0001, TypeEnum.NUMBER),
|
|
198
|
+
({"key": "value"}, TypeEnum.JSON),
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
for test_value, value_type in tests:
|
|
202
|
+
result = self.client.variable(self.test_user, "badKey", test_value)
|
|
203
|
+
self.assertIsNotNone(result)
|
|
204
|
+
self.assertEqual(result.key, "badKey")
|
|
205
|
+
self.assertTrue(result.isDefaulted)
|
|
206
|
+
self.assertEqual(result.defaultValue, test_value)
|
|
207
|
+
self.assertEqual(result.value, test_value)
|
|
208
|
+
self.assertEqual(result.type, value_type)
|
|
209
|
+
|
|
210
|
+
@responses.activate
|
|
211
|
+
def test_variable_with_bucketing(self):
|
|
212
|
+
self.setup_client()
|
|
213
|
+
|
|
214
|
+
user = DevCycleUser(user_id="1234")
|
|
215
|
+
|
|
216
|
+
tests = [
|
|
217
|
+
("string-var", "default_value", "variationOn", TypeEnum.STRING),
|
|
218
|
+
("a-cool-new-feature", False, True, TypeEnum.BOOLEAN),
|
|
219
|
+
("num-var", 0, 12345, TypeEnum.NUMBER),
|
|
220
|
+
(
|
|
221
|
+
"json-var",
|
|
222
|
+
{"default": "value"},
|
|
223
|
+
{
|
|
224
|
+
"displayText": "This variation is on",
|
|
225
|
+
"showDialog": True,
|
|
226
|
+
"maxUsers": 100,
|
|
227
|
+
},
|
|
228
|
+
TypeEnum.JSON,
|
|
229
|
+
),
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
for key, default_val, expected, var_type in tests:
|
|
233
|
+
result = self.client.variable(user, key, default_val)
|
|
234
|
+
self.assertIsNotNone(result, msg="Test key: " + key)
|
|
235
|
+
self.assertEqual(result.key, key, msg="Test key: " + key)
|
|
236
|
+
self.assertFalse(result.isDefaulted, msg="Test key: " + key)
|
|
237
|
+
if var_type == TypeEnum.JSON:
|
|
238
|
+
self.assertDictEqual(result.value, expected, msg="Test key: " + key)
|
|
239
|
+
else:
|
|
240
|
+
self.assertEqual(result.value, expected, msg="Test key: " + key)
|
|
241
|
+
|
|
242
|
+
@responses.activate
|
|
243
|
+
def test_variable_with_events(self):
|
|
244
|
+
self.options.disable_automatic_event_logging = False
|
|
245
|
+
self.options.disable_custom_event_logging = False
|
|
246
|
+
self.options.event_flush_interval_ms = 0
|
|
247
|
+
self.setup_client()
|
|
248
|
+
user = DevCycleUser(user_id="1234")
|
|
249
|
+
self.client.variable(user, "string-var", "default_value")
|
|
250
|
+
|
|
251
|
+
@responses.activate
|
|
252
|
+
def test_all_features(self):
|
|
253
|
+
self.setup_client()
|
|
254
|
+
|
|
255
|
+
user = DevCycleUser(user_id="1234")
|
|
256
|
+
|
|
257
|
+
result = self.client.all_features(user)
|
|
258
|
+
self.assertEqual(
|
|
259
|
+
result,
|
|
260
|
+
{
|
|
261
|
+
"a-cool-new-feature": Feature(
|
|
262
|
+
_id="62fbf6566f1ba302829f9e32",
|
|
263
|
+
key="a-cool-new-feature",
|
|
264
|
+
type="release",
|
|
265
|
+
_variation="62fbf6566f1ba302829f9e39",
|
|
266
|
+
variationName="VariationOn",
|
|
267
|
+
variationKey="variation-on",
|
|
268
|
+
evalReason=None,
|
|
269
|
+
)
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
@responses.activate
|
|
274
|
+
@patch.object(
|
|
275
|
+
LocalBucketing,
|
|
276
|
+
"generate_bucketed_config",
|
|
277
|
+
side_effect=MalformedConfigError("bad config"),
|
|
278
|
+
)
|
|
279
|
+
def test_all_features_exception(self, _):
|
|
280
|
+
self.setup_client()
|
|
281
|
+
|
|
282
|
+
user = DevCycleUser(user_id="1234")
|
|
283
|
+
|
|
284
|
+
result = self.client.all_features(user)
|
|
285
|
+
self.assertEqual(result, {})
|
|
286
|
+
|
|
287
|
+
@responses.activate
|
|
288
|
+
def test_all_variables(self):
|
|
289
|
+
self.setup_client()
|
|
290
|
+
|
|
291
|
+
user = DevCycleUser(user_id="1234")
|
|
292
|
+
|
|
293
|
+
result = self.client.all_variables(user)
|
|
294
|
+
|
|
295
|
+
expected_variables = {
|
|
296
|
+
"a-cool-new-feature": Variable(
|
|
297
|
+
_id="62fbf6566f1ba302829f9e34",
|
|
298
|
+
key="a-cool-new-feature",
|
|
299
|
+
type="Boolean",
|
|
300
|
+
value=True,
|
|
301
|
+
isDefaulted=None,
|
|
302
|
+
defaultValue=None,
|
|
303
|
+
evalReason=None,
|
|
304
|
+
),
|
|
305
|
+
"string-var": Variable(
|
|
306
|
+
_id="63125320a4719939fd57cb2b",
|
|
307
|
+
key="string-var",
|
|
308
|
+
type="String",
|
|
309
|
+
value="variationOn",
|
|
310
|
+
isDefaulted=None,
|
|
311
|
+
defaultValue=None,
|
|
312
|
+
evalReason=None,
|
|
313
|
+
),
|
|
314
|
+
"json-var": Variable(
|
|
315
|
+
_id="64372363125123fca69d3f7b",
|
|
316
|
+
key="json-var",
|
|
317
|
+
type="JSON",
|
|
318
|
+
value={
|
|
319
|
+
"displayText": "This variation is on",
|
|
320
|
+
"showDialog": True,
|
|
321
|
+
"maxUsers": 100,
|
|
322
|
+
},
|
|
323
|
+
isDefaulted=None,
|
|
324
|
+
defaultValue=None,
|
|
325
|
+
evalReason=None,
|
|
326
|
+
),
|
|
327
|
+
"num-var": Variable(
|
|
328
|
+
_id="65272363125123fca69d3a7d",
|
|
329
|
+
key="num-var",
|
|
330
|
+
type="Number",
|
|
331
|
+
value=12345,
|
|
332
|
+
isDefaulted=None,
|
|
333
|
+
defaultValue=None,
|
|
334
|
+
evalReason=None,
|
|
335
|
+
),
|
|
336
|
+
}
|
|
337
|
+
self.assertEqual(result, expected_variables)
|
|
338
|
+
|
|
339
|
+
@responses.activate
|
|
340
|
+
@patch.object(
|
|
341
|
+
LocalBucketing,
|
|
342
|
+
"generate_bucketed_config",
|
|
343
|
+
side_effect=MalformedConfigError("bad config"),
|
|
344
|
+
)
|
|
345
|
+
def test_all_variables_exception(self, _):
|
|
346
|
+
self.setup_client()
|
|
347
|
+
|
|
348
|
+
user = DevCycleUser(user_id="1234")
|
|
349
|
+
|
|
350
|
+
result = self.client.all_variables(user)
|
|
351
|
+
self.assertEqual(result, {})
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _benchmark_variable_call(client: DevCycleLocalClient, user: DevCycleUser, key: str):
|
|
355
|
+
return client.variable(user, key, "default_value")
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
@responses.activate
|
|
359
|
+
def test_benchmark_variable_call(benchmark):
|
|
360
|
+
sdk_key = "dvc_server_949e4962-c624-4d20-a1ea-7f2501b2ba79"
|
|
361
|
+
test_config_json = small_config_json()
|
|
362
|
+
test_etag = "2f71454e-3279-4ca7-a8e7-802ce97bef43"
|
|
363
|
+
|
|
364
|
+
config_url = "http://localhost/config/v1/server/" + sdk_key + ".json"
|
|
365
|
+
|
|
366
|
+
responses.add(
|
|
367
|
+
responses.GET,
|
|
368
|
+
config_url,
|
|
369
|
+
headers={"ETag": test_etag},
|
|
370
|
+
json=test_config_json,
|
|
371
|
+
status=200,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
options = DevCycleLocalOptions(
|
|
375
|
+
config_polling_interval_ms=5000,
|
|
376
|
+
config_cdn_uri="http://localhost/",
|
|
377
|
+
disable_custom_event_logging=True,
|
|
378
|
+
disable_automatic_event_logging=True,
|
|
379
|
+
)
|
|
380
|
+
user = DevCycleUser(user_id="test_user_id")
|
|
381
|
+
|
|
382
|
+
client = DevCycleLocalClient(sdk_key, options)
|
|
383
|
+
while not client.is_initialized():
|
|
384
|
+
time.sleep(0.05)
|
|
385
|
+
|
|
386
|
+
# benchmark is a pytest fixture provided by pytest-benchmark that handles timing the provided callable
|
|
387
|
+
result = benchmark(_benchmark_variable_call, client, user, "string-var")
|
|
388
|
+
|
|
389
|
+
assert result is not None
|
|
390
|
+
assert result.key == "string-var"
|
|
391
|
+
assert result.isDefaulted is False
|
|
392
|
+
assert result.value == "variationOn"
|
|
393
|
+
assert result.type == TypeEnum.STRING
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
unittest.main()
|
test/util/test_utils.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from devcycle_python_sdk.models.variable import TypeEnum
|
|
6
|
+
from devcycle_python_sdk.models.user import DevCycleUser
|
|
7
|
+
import devcycle_python_sdk.protobuf.utils as utils
|
|
8
|
+
import devcycle_python_sdk.protobuf.variableForUserParams_pb2 as pb2
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class VersionTest(unittest.TestCase):
|
|
14
|
+
def test_create_nullable_double(self):
|
|
15
|
+
result = utils.create_nullable_double(None)
|
|
16
|
+
self.assertIsNotNone(result)
|
|
17
|
+
self.assertTrue(result.isNull)
|
|
18
|
+
|
|
19
|
+
result = utils.create_nullable_double(99.0)
|
|
20
|
+
self.assertIsNotNone(result)
|
|
21
|
+
self.assertFalse(result.isNull)
|
|
22
|
+
self.assertEqual(result.value, 99.0)
|
|
23
|
+
|
|
24
|
+
def test_create_nullable_string(self):
|
|
25
|
+
result = utils.create_nullable_string(None)
|
|
26
|
+
self.assertIsNotNone(result)
|
|
27
|
+
self.assertTrue(result.isNull)
|
|
28
|
+
|
|
29
|
+
result = utils.create_nullable_string("")
|
|
30
|
+
self.assertIsNotNone(result)
|
|
31
|
+
self.assertFalse(result.isNull)
|
|
32
|
+
self.assertEqual(result.value, "")
|
|
33
|
+
|
|
34
|
+
result = utils.create_nullable_string("test value")
|
|
35
|
+
self.assertIsNotNone(result)
|
|
36
|
+
self.assertFalse(result.isNull)
|
|
37
|
+
self.assertEqual(result.value, "test value")
|
|
38
|
+
|
|
39
|
+
def test_create_nullable_custom_data(self):
|
|
40
|
+
result = utils.create_nullable_custom_data(None)
|
|
41
|
+
self.assertIsNotNone(result)
|
|
42
|
+
self.assertTrue(result.isNull)
|
|
43
|
+
|
|
44
|
+
result = utils.create_nullable_custom_data(dict())
|
|
45
|
+
self.assertIsNotNone(result)
|
|
46
|
+
self.assertTrue(result.isNull)
|
|
47
|
+
|
|
48
|
+
result = utils.create_nullable_custom_data({"strProp": "test value"})
|
|
49
|
+
self.assertIsNotNone(result)
|
|
50
|
+
self.assertFalse(result.isNull)
|
|
51
|
+
self.assertEqual(result.value["strProp"].type, pb2.CustomDataType.Str)
|
|
52
|
+
self.assertEqual(result.value["strProp"].stringValue, "test value")
|
|
53
|
+
|
|
54
|
+
result = utils.create_nullable_custom_data({"boolProp": False})
|
|
55
|
+
self.assertIsNotNone(result)
|
|
56
|
+
self.assertFalse(result.isNull)
|
|
57
|
+
self.assertEqual(result.value["boolProp"].type, pb2.CustomDataType.Bool)
|
|
58
|
+
self.assertEqual(result.value["boolProp"].boolValue, False)
|
|
59
|
+
|
|
60
|
+
result = utils.create_nullable_custom_data({"numProp": 1234.0})
|
|
61
|
+
self.assertIsNotNone(result)
|
|
62
|
+
self.assertFalse(result.isNull)
|
|
63
|
+
self.assertEqual(result.value["numProp"].type, pb2.CustomDataType.Num)
|
|
64
|
+
self.assertEqual(result.value["numProp"].doubleValue, 1234.0)
|
|
65
|
+
|
|
66
|
+
def test_create_variable_string(self):
|
|
67
|
+
sdk_var = pb2.SDKVariable_PB(
|
|
68
|
+
_id="test id",
|
|
69
|
+
key="test key",
|
|
70
|
+
stringValue="test value",
|
|
71
|
+
type=pb2.VariableType_PB.String,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
var = utils.create_variable(sdk_var, "default value")
|
|
75
|
+
self.assertIsNotNone(var)
|
|
76
|
+
self.assertEqual(var.type, TypeEnum.STRING)
|
|
77
|
+
self.assertIsNone(var._id)
|
|
78
|
+
self.assertEqual(var.key, sdk_var.key)
|
|
79
|
+
self.assertEqual(var.value, sdk_var.stringValue)
|
|
80
|
+
self.assertEqual(var.defaultValue, "default value")
|
|
81
|
+
self.assertFalse(var.isDefaulted)
|
|
82
|
+
|
|
83
|
+
def test_create_variable_boolean(self):
|
|
84
|
+
sdk_var = pb2.SDKVariable_PB(
|
|
85
|
+
_id="test id",
|
|
86
|
+
key="test key",
|
|
87
|
+
boolValue=True,
|
|
88
|
+
type=pb2.VariableType_PB.Boolean,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
var = utils.create_variable(sdk_var, False)
|
|
92
|
+
self.assertIsNotNone(var)
|
|
93
|
+
self.assertEqual(var.type, TypeEnum.BOOLEAN)
|
|
94
|
+
self.assertIsNone(var._id)
|
|
95
|
+
self.assertEqual(var.key, sdk_var.key)
|
|
96
|
+
self.assertEqual(var.value, sdk_var.boolValue)
|
|
97
|
+
self.assertEqual(var.defaultValue, False)
|
|
98
|
+
self.assertFalse(var.isDefaulted)
|
|
99
|
+
|
|
100
|
+
def test_create_variable_number(self):
|
|
101
|
+
sdk_var = pb2.SDKVariable_PB(
|
|
102
|
+
_id="test id",
|
|
103
|
+
key="test key",
|
|
104
|
+
doubleValue=99.99,
|
|
105
|
+
type=pb2.VariableType_PB.Number,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
var = utils.create_variable(sdk_var, 0)
|
|
109
|
+
self.assertIsNotNone(var)
|
|
110
|
+
self.assertEqual(var.type, TypeEnum.NUMBER)
|
|
111
|
+
self.assertIsNone(var._id)
|
|
112
|
+
self.assertEqual(var.key, sdk_var.key)
|
|
113
|
+
self.assertEqual(var.value, sdk_var.doubleValue)
|
|
114
|
+
self.assertEqual(var.defaultValue, 0)
|
|
115
|
+
self.assertFalse(var.isDefaulted)
|
|
116
|
+
|
|
117
|
+
def test_create_variable_json(self):
|
|
118
|
+
sdk_var = pb2.SDKVariable_PB(
|
|
119
|
+
_id="test id",
|
|
120
|
+
key="test key",
|
|
121
|
+
stringValue='{"strProp": "test value"}',
|
|
122
|
+
type=pb2.VariableType_PB.JSON,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
var = utils.create_variable(sdk_var, {})
|
|
126
|
+
self.assertIsNotNone(var)
|
|
127
|
+
self.assertEqual(var.type, TypeEnum.JSON)
|
|
128
|
+
self.assertIsNone(var._id)
|
|
129
|
+
self.assertEqual(var.key, sdk_var.key)
|
|
130
|
+
self.assertDictEqual(var.value, {"strProp": "test value"})
|
|
131
|
+
self.assertDictEqual(var.defaultValue, {})
|
|
132
|
+
self.assertFalse(var.isDefaulted)
|
|
133
|
+
|
|
134
|
+
def test_create_dvcuser_pb_bad_app_build(self):
|
|
135
|
+
user = DevCycleUser(
|
|
136
|
+
user_id="test id",
|
|
137
|
+
appBuild=None,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
result = utils.create_dvcuser_pb(user)
|
|
141
|
+
self.assertIsNotNone(result)
|
|
142
|
+
self.assertEqual(result.user_id, user.user_id)
|
|
143
|
+
self.assertTrue(result.appBuild.isNull)
|
|
144
|
+
|
|
145
|
+
user = DevCycleUser(
|
|
146
|
+
user_id="test id",
|
|
147
|
+
appBuild="NotANumberAtAll",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
result = utils.create_dvcuser_pb(user)
|
|
151
|
+
self.assertIsNotNone(result)
|
|
152
|
+
self.assertEqual(result.user_id, user.user_id)
|
|
153
|
+
self.assertTrue(result.appBuild.isNull)
|
|
154
|
+
|
|
155
|
+
def test_create_dvcuser_pb(self):
|
|
156
|
+
user = DevCycleUser(
|
|
157
|
+
user_id="test id",
|
|
158
|
+
name="test name",
|
|
159
|
+
email="test email",
|
|
160
|
+
customData={"a": "value1", "b": 0.0, "c": False},
|
|
161
|
+
privateCustomData={"x": "value2", "y": 1234.0, "z": False},
|
|
162
|
+
appBuild="1.17",
|
|
163
|
+
appVersion="1.0.0",
|
|
164
|
+
country="US",
|
|
165
|
+
language=None,
|
|
166
|
+
deviceModel="iPhone X",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
result = utils.create_dvcuser_pb(user)
|
|
170
|
+
self.assertIsNotNone(result)
|
|
171
|
+
self.assertEqual(result.user_id, user.user_id)
|
|
172
|
+
self.assertEqual(result.name.value, user.name)
|
|
173
|
+
self.assertEqual(result.email.value, user.email)
|
|
174
|
+
self.assertTrue(result.language.isNull)
|
|
175
|
+
self.assertEqual(result.appBuild.value, 1.17)
|
|
176
|
+
|
|
177
|
+
self.assertEqual(result.customData.value["a"].type, pb2.CustomDataType.Str)
|
|
178
|
+
self.assertEqual(result.customData.value["a"].stringValue, "value1")
|
|
179
|
+
|
|
180
|
+
self.assertEqual(
|
|
181
|
+
result.privateCustomData.value["x"].type, pb2.CustomDataType.Str
|
|
182
|
+
)
|
|
183
|
+
self.assertEqual(result.privateCustomData.value["x"].stringValue, "value2")
|
test/util/test_version.py
CHANGED
|
@@ -9,10 +9,10 @@ logger = logging.getLogger(__name__)
|
|
|
9
9
|
|
|
10
10
|
class VersionTest(unittest.TestCase):
|
|
11
11
|
def test_sdk_version(self):
|
|
12
|
-
expected_version = "
|
|
12
|
+
expected_version = "3.0.0"
|
|
13
13
|
version = sdk_version()
|
|
14
14
|
self.assertEqual(version, expected_version)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
if __name__ ==
|
|
17
|
+
if __name__ == "__main__":
|
|
18
18
|
unittest.main()
|