Skip to content
This repository was archived by the owner on Sep 28, 2023. It is now read-only.

Commit 8a51a1f

Browse files
author
Andy Harris
committed
add test coverage for all API Resource classes
1 parent 7462c45 commit 8a51a1f

3 files changed

Lines changed: 708 additions & 10 deletions

File tree

optimizely/__init__.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
class APIResource(object):
1111
endpoint = ''
1212

13-
def __init__(self, params):
14-
if type(params) == dict:
15-
for (k, v) in params.iteritems():
13+
def __init__(self, param):
14+
if type(param) == int:
15+
self.__init__(self.get(param).__dict__)
16+
elif type(param) == dict:
17+
for (k, v) in param.iteritems():
1618
self.__setattr__(k, v)
1719
else:
1820
raise ValueError('%s can only be initiated with a dict.' % self.__class__.__name__)
@@ -115,12 +117,15 @@ def variations(self):
115117

116118
def add_goal(self, gid):
117119
goal = Goal.get(gid)
118-
return Goal.from_api_response(Goal.update(goal.id, {'experiment_ids': goal.experiment_ids.append(self.id)}))
120+
experiment_ids = set(goal.experiment_ids)
121+
experiment_ids.add(self.id)
122+
return Goal.update(goal.id, {'experiment_ids': list(experiment_ids)})
119123

120124
def remove_goal(self, gid):
121125
goal = Goal.get(gid)
122-
experiment_ids = list(set(goal.experiment_ids).remove(self.id))
123-
return Goal.from_api_response(Goal.update(goal.id, {'experiment_ids': experiment_ids}))
126+
experiment_ids = set(goal.experiment_ids)
127+
experiment_ids.remove(self.id)
128+
return Goal.update(goal.id, {'experiment_ids': list(experiment_ids)})
124129

125130

126131
class Result(APIResource):
@@ -137,15 +142,15 @@ def get(cls, pid):
137142

138143
@classmethod
139144
def create(cls, data):
140-
return NotImplementedError('There is no method to create a result.')
145+
raise NotImplementedError('There is no method to create a result.')
141146

142147
@classmethod
143148
def update(cls, pid, data):
144-
return NotImplementedError('There is no method to update a result.')
149+
raise NotImplementedError('There is no method to update a result.')
145150

146151
@classmethod
147152
def delete(cls, pid):
148-
return NotImplementedError('There is no method to delete a result.')
153+
raise NotImplementedError('There is no method to delete a result.')
149154

150155

151156
class Variation(APIResource):
@@ -187,4 +192,8 @@ def list(cls):
187192
def create(cls, data):
188193
return cls.from_api_response(requests.post(api_base + 'projects/' + str(data['project_id']) + '/' +
189194
cls.endpoint, data=json.dumps(data),
190-
headers={'Token': api_key, 'Content-Type': 'application/json'}))
195+
headers={'Token': api_key, 'Content-Type': 'application/json'}))
196+
197+
@classmethod
198+
def delete(cls, pid):
199+
raise NotImplementedError('Audiences may not be deleted through the API.')

tests/api_mock.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import json
2+
import mock
3+
4+
import optimizely
5+
6+
7+
class APIResponseMock():
8+
""" A class used to mock responses from the requests module
9+
"""
10+
def __init__(self, status_code=None, data=None, text=None):
11+
self.status_code = status_code
12+
self.json = mock.Mock(return_value=data)
13+
self.text = text
14+
15+
16+
class APIMock():
17+
""" A class used to mock the Optimizely API. Its get, post, put, and delete functions can be used to mock the
18+
requests module.
19+
"""
20+
def __init__(self, get_responses=None, post_responses=None, put_responses=None, delete_responses=None,
21+
*args, **kwargs):
22+
self.get_responses = get_responses or {}
23+
self.post_responses = post_responses or {}
24+
self.put_responses = put_responses or {}
25+
self.delete_responses = delete_responses or {}
26+
27+
def get(self, url, **kwargs):
28+
return self.get_responses[url]
29+
30+
def post(self, url, **kwargs):
31+
return self.post_responses[url]
32+
33+
def put(self, url, **kwargs):
34+
return self.put_responses[url]
35+
36+
def delete(self, url, **kwargs):
37+
return self.delete_responses[url]
38+
39+
40+
class TestResource(object):
41+
""" Base class used for testing Optimizely API Resources. It includes the base tests for standard/shared API
42+
requests
43+
"""
44+
45+
def create_mocks(self):
46+
# create updated data mocks
47+
self.sample_data_updated = self.sample_data.copy()
48+
self.sample_data_updated.update(self.sample_updates)
49+
50+
# mock requests module
51+
self.mock_api_get = APIResponseMock(200, self.sample_data)
52+
self.mock_api_create = APIResponseMock(201, self.sample_data)
53+
self.mock_api_list = APIResponseMock(200, [self.sample_data, self.sample_data])
54+
self.mock_api_update = APIResponseMock(200, self.sample_data_updated)
55+
self.mock_api_delete = APIResponseMock(204, None)
56+
self.mock_api = APIMock(get_responses={
57+
optimizely.api_base + self.test_resource.endpoint + str(self.id): self.mock_api_get,
58+
optimizely.api_base + self.test_resource.endpoint: self.mock_api_list
59+
}, post_responses={
60+
optimizely.api_base + self.test_resource.endpoint: self.mock_api_create
61+
}, put_responses={
62+
optimizely.api_base + self.test_resource.endpoint + str(self.id): self.mock_api_update
63+
}, delete_responses={
64+
optimizely.api_base + self.test_resource.endpoint + str(self.id): self.mock_api_delete
65+
})
66+
requests_mock = mock.Mock()
67+
requests_mock.get = mock.Mock(side_effect=self.mock_api.get)
68+
requests_mock.post = mock.Mock(side_effect=self.mock_api.post)
69+
requests_mock.put = mock.Mock(side_effect=self.mock_api.put)
70+
requests_mock.delete = mock.Mock(side_effect=self.mock_api.delete)
71+
self.p = mock.patch('optimizely.requests', new=requests_mock)
72+
self.p.start()
73+
74+
def tearDown(self):
75+
# if optimizely.requests has been patched, unpatch at the end of each test
76+
if hasattr(self, 'p'):
77+
self.p.stop()
78+
79+
def test_list(self):
80+
""" Tests that .list() successfully retrieves two objects
81+
"""
82+
# call .list()
83+
resources = self.test_resource.list()
84+
85+
# ensure that the correct GET request was made
86+
optimizely.requests.get.assert_called_with(optimizely.api_base + self.test_resource.endpoint,
87+
headers={'Token': optimizely.api_key})
88+
89+
# ensure that values and types are correct
90+
self.assertEqual(2, len(resources))
91+
for resource in resources:
92+
for k, v in self.sample_data.iteritems():
93+
self.assertEquals(v, resource.__getattribute__(k))
94+
self.assertIsInstance(resource, self.test_resource)
95+
96+
def test_get(self):
97+
""" Tests that .get() successfully retrieves the correct object
98+
"""
99+
# call .get()
100+
resource = self.test_resource.get(self.id)
101+
102+
# ensure that the correct GET request was made
103+
optimizely.requests.get.assert_called_with(optimizely.api_base + self.test_resource.endpoint +
104+
str(self.id), headers={'Token': optimizely.api_key})
105+
106+
# ensure that values and type are correct
107+
for k, v in self.sample_data.iteritems():
108+
self.assertEquals(v, resource.__getattribute__(k))
109+
self.assertIsInstance(resource, self.test_resource)
110+
111+
def test_create(self):
112+
""" Tests that .create() successfully creates and retrieves the correct object
113+
"""
114+
# call .create()
115+
resource = self.test_resource.create(self.sample_data)
116+
117+
# ensure that the correct POST request was made
118+
optimizely.requests.post.assert_called_with(optimizely.api_base + self.test_resource.endpoint,
119+
data=json.dumps(self.sample_data),
120+
headers={'Token': optimizely.api_key,
121+
'Content-Type': 'application/json'})
122+
123+
# ensure that values and type are correct
124+
for k, v in self.sample_data.iteritems():
125+
self.assertEquals(v, resource.__getattribute__(k))
126+
self.assertIsInstance(resource, self.test_resource)
127+
128+
def test_update(self):
129+
""" Tests that .update() successfully creates and retrieves the correct object
130+
"""
131+
# call .create()
132+
resource = self.test_resource.update(self.id, self.sample_updates)
133+
134+
# ensure that the correct PUT request was made
135+
optimizely.requests.put.assert_called_with(optimizely.api_base + self.test_resource.endpoint + str(self.id),
136+
data=json.dumps(self.sample_updates),
137+
headers={'Token': optimizely.api_key,
138+
'Content-Type': 'application/json'})
139+
140+
# ensure that values and type are correct
141+
for k, v in self.sample_data_updated.iteritems():
142+
self.assertEquals(v, resource.__getattribute__(k))
143+
self.assertIsInstance(resource, self.test_resource)
144+
145+
def test_delete(self):
146+
""" Tests that .delete() calls the correct endpoint and that it returns None upon success
147+
"""
148+
# call .delete()
149+
return_value = self.test_resource.delete(self.id)
150+
151+
# ensure that the correct DELETE request was made
152+
optimizely.requests.delete.assert_called_with(optimizely.api_base + self.test_resource.endpoint +
153+
str(self.id), headers={'Token': optimizely.api_key})
154+
155+
# ensure that None is returned
156+
self.assertIsNone(return_value)

0 commit comments

Comments
 (0)