Merge lp:~mandel/desktopcouch/mock_db_for_records into lp:desktopcouch
- mock_db_for_records
- Merge into trunk
Proposed by
Manuel de la Peña
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Manuel de la Peña | ||||||||||||
Approved revision: | 251 | ||||||||||||
Merged at revision: | 248 | ||||||||||||
Proposed branch: | lp:~mandel/desktopcouch/mock_db_for_records | ||||||||||||
Merge into: | lp:desktopcouch | ||||||||||||
Diff against target: |
726 lines (+561/-28) 4 files modified
desktopcouch/application/server.py (+2/-2) desktopcouch/records/database.py (+39/-25) desktopcouch/records/tests/test_mocked_server.py (+519/-0) desktopcouch/records/tests/test_server.py (+1/-1) |
||||||||||||
To merge this branch: | bzr merge lp:~mandel/desktopcouch/mock_db_for_records | ||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Approve | ||
Vincenzo Di Somma (community) | Approve | ||
Review via email: mp+45262@code.launchpad.net |
Commit message
Moved trash db to be an instance variable for bug #697661
Fixed how deleted records ids are generated for bug #697636
Added unit tests in which the db access is mocked for bug #674587
Description of the change
Moved trash db to be an instance variable for bug #697661
Fixed how deleted records ids are generated for bug #697636
Added unit tests in which the db access is mocked for bug #674587
To post a comment you must log in.
Revision history for this message
Vincenzo Di Somma (vds) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'desktopcouch/application/server.py' | |||
2 | --- desktopcouch/application/server.py 2010-11-26 20:30:30 +0000 | |||
3 | +++ desktopcouch/application/server.py 2011-01-05 17:11:33 +0000 | |||
4 | @@ -64,7 +64,7 @@ | |||
5 | 64 | 64 | ||
6 | 65 | def __init__(self, database, uri=None, record_factory=None, | 65 | def __init__(self, database, uri=None, record_factory=None, |
7 | 66 | create=False, server_class=DesktopServer, oauth_tokens=None, | 66 | create=False, server_class=DesktopServer, oauth_tokens=None, |
9 | 67 | ctx=None): | 67 | ctx=None, views_factory=None): |
10 | 68 | if ctx is None: | 68 | if ctx is None: |
11 | 69 | ctx = DEFAULT_CONTEXT | 69 | ctx = DEFAULT_CONTEXT |
12 | 70 | self.ctx = ctx | 70 | self.ctx = ctx |
13 | @@ -72,7 +72,7 @@ | |||
14 | 72 | super(DesktopDatabase, self).__init__( | 72 | super(DesktopDatabase, self).__init__( |
15 | 73 | database, uri, record_factory=record_factory, | 73 | database, uri, record_factory=record_factory, |
16 | 74 | create=create, server_class=server_class, | 74 | create=create, server_class=server_class, |
18 | 75 | oauth_tokens=oauth_tokens, ctx=ctx) | 75 | oauth_tokens=oauth_tokens, ctx=ctx, views_factory=views_factory) |
19 | 76 | 76 | ||
20 | 77 | # pylint: disable=W0221 | 77 | # pylint: disable=W0221 |
21 | 78 | # Arguments number differs from overridden method | 78 | # Arguments number differs from overridden method |
22 | 79 | 79 | ||
23 | === modified file 'desktopcouch/records/database.py' | |||
24 | --- desktopcouch/records/database.py 2011-01-05 13:48:31 +0000 | |||
25 | +++ desktopcouch/records/database.py 2011-01-05 17:11:33 +0000 | |||
26 | @@ -26,7 +26,6 @@ | |||
27 | 26 | # pylint: disable=W0402 | 26 | # pylint: disable=W0402 |
28 | 27 | import string | 27 | import string |
29 | 28 | # pylint: enable=W0402 | 28 | # pylint: enable=W0402 |
30 | 29 | import uuid | ||
31 | 30 | import warnings | 29 | import warnings |
32 | 31 | 30 | ||
33 | 32 | from time import time | 31 | from time import time |
34 | @@ -106,7 +105,12 @@ | |||
35 | 106 | """A desktopcouch.records specific CouchDb database.""" | 105 | """A desktopcouch.records specific CouchDb database.""" |
36 | 107 | 106 | ||
37 | 108 | def __init__(self, database, uri, record_factory=None, create=False, | 107 | def __init__(self, database, uri, record_factory=None, create=False, |
39 | 109 | server_class=Server, **server_class_extras): | 108 | views_factory=None, server_class=Server, |
40 | 109 | **server_class_extras): | ||
41 | 110 | if views_factory is None: | ||
42 | 111 | self._views_factory = ViewDefinition | ||
43 | 112 | else: | ||
44 | 113 | self._views_factory = views_factory | ||
45 | 110 | self.server_uri = uri | 114 | self.server_uri = uri |
46 | 111 | self._database_name = database | 115 | self._database_name = database |
47 | 112 | self.record_factory = record_factory or Record | 116 | self.record_factory = record_factory or Record |
48 | @@ -115,6 +119,7 @@ | |||
49 | 115 | self._server_class_extras = server_class_extras | 119 | self._server_class_extras = server_class_extras |
50 | 116 | self._server = None | 120 | self._server = None |
51 | 117 | self.db = None | 121 | self.db = None |
52 | 122 | self._dctrash = None | ||
53 | 118 | self._reconnect() | 123 | self._reconnect() |
54 | 119 | self._changes_since = self.db.info()["update_seq"] | 124 | self._changes_since = self.db.info()["update_seq"] |
55 | 120 | self._changes_last_used = 0 # Immediate run works. | 125 | self._changes_last_used = 0 # Immediate run works. |
56 | @@ -182,40 +187,45 @@ | |||
57 | 182 | 187 | ||
58 | 183 | def put_record(self, record): | 188 | def put_record(self, record): |
59 | 184 | """Put a record in back end storage.""" | 189 | """Put a record in back end storage.""" |
61 | 185 | if not record.record_id: | 190 | record_id = record.record_id |
62 | 191 | record_data = record._data # pylint: disable=W0212 | ||
63 | 192 | if not record_id: | ||
64 | 186 | # Do not rely on couchdb to create an ID for us. | 193 | # Do not rely on couchdb to create an ID for us. |
65 | 187 | # pylint: disable=E1101 | 194 | # pylint: disable=E1101 |
67 | 188 | record.record_id = base_n(uuid4().int, 62) | 195 | record_id = base_n(uuid4().int, 62) |
68 | 196 | record.record_id = record_id | ||
69 | 189 | # pylint: enable=E1101 | 197 | # pylint: enable=E1101 |
71 | 190 | self.db[record.record_id] = record._data # pylint: disable=W0212 | 198 | self.db[record_id] = record_data |
72 | 191 | 199 | ||
73 | 192 | # pylint: disable=W0212 | 200 | # pylint: disable=W0212 |
74 | 193 | for attachment_name in getattr(record, "_detached", []): | 201 | for attachment_name in getattr(record, "_detached", []): |
76 | 194 | self.db.delete_attachment(record._data, attachment_name) | 202 | self.db.delete_attachment(record_data, attachment_name) |
77 | 195 | 203 | ||
78 | 196 | for attachment_name in record.list_attachments(): | 204 | for attachment_name in record.list_attachments(): |
79 | 197 | data, content_type = record.attachment_data(attachment_name) | 205 | data, content_type = record.attachment_data(attachment_name) |
81 | 198 | self.db.put_attachment(record._data, | 206 | self.db.put_attachment(record_data, |
82 | 199 | data, | 207 | data, |
83 | 200 | attachment_name, | 208 | attachment_name, |
84 | 201 | content_type) | 209 | content_type) |
85 | 202 | # pylint: enable=W0212 | 210 | # pylint: enable=W0212 |
86 | 203 | 211 | ||
88 | 204 | return record.record_id | 212 | return record_id |
89 | 205 | 213 | ||
90 | 206 | def put_records_batch(self, batch): | 214 | def put_records_batch(self, batch): |
91 | 207 | """Put a batch of records in back end storage.""" | 215 | """Put a batch of records in back end storage.""" |
92 | 208 | # used to access fast the record | 216 | # used to access fast the record |
93 | 209 | records_hash = {} | 217 | records_hash = {} |
94 | 210 | for record in batch: | 218 | for record in batch: |
96 | 211 | if not record.record_id: | 219 | record_id = record.record_id |
97 | 220 | if not record_id: | ||
98 | 212 | # Do not rely on couchdb to create an ID for us. | 221 | # Do not rely on couchdb to create an ID for us. |
99 | 213 | # pylint: disable=E1101 | 222 | # pylint: disable=E1101 |
101 | 214 | record.record_id = base_n(uuid4().int, 62) | 223 | record_id = base_n(uuid4().int, 62) |
102 | 224 | record.record_id = record_id | ||
103 | 215 | # pylint: enable=E1101 | 225 | # pylint: enable=E1101 |
105 | 216 | records_hash[record.record_id] = record | 226 | records_hash[record_id] = record |
106 | 217 | # although with a single record we need to test for the | 227 | # although with a single record we need to test for the |
108 | 218 | # revisison, with a batch we do not, but we have to make sure | 228 | # revision, with a batch we do not, but we have to make sure |
109 | 219 | # that we did not get an error | 229 | # that we did not get an error |
110 | 220 | # pylint: disable=W0212 | 230 | # pylint: disable=W0212 |
111 | 221 | batch_put_result = self.db.update([record._data for record in batch]) | 231 | batch_put_result = self.db.update([record._data for record in batch]) |
112 | @@ -223,15 +233,16 @@ | |||
113 | 223 | success, docid, rev_or_exc = current_tuple | 233 | success, docid, rev_or_exc = current_tuple |
114 | 224 | if success: | 234 | if success: |
115 | 225 | record = records_hash[docid] | 235 | record = records_hash[docid] |
116 | 236 | record_data = record._data | ||
117 | 226 | # set the new rev | 237 | # set the new rev |
119 | 227 | record._data["_rev"] = rev_or_exc | 238 | record_data["_rev"] = rev_or_exc |
120 | 228 | for attachment_name in getattr(record, "_detached", []): | 239 | for attachment_name in getattr(record, "_detached", []): |
122 | 229 | self.db.delete_attachment(record._data, attachment_name) | 240 | self.db.delete_attachment(record_data, attachment_name) |
123 | 230 | for attachment_name in record.list_attachments(): | 241 | for attachment_name in record.list_attachments(): |
124 | 231 | data, content_type = record.attachment_data( | 242 | data, content_type = record.attachment_data( |
125 | 232 | attachment_name) | 243 | attachment_name) |
126 | 233 | self.db.put_attachment( | 244 | self.db.put_attachment( |
128 | 234 | {"_id": record.record_id, "_rev": record["_rev"]}, | 245 | {"_id": docid, "_rev": rev_or_exc}, |
129 | 235 | data, attachment_name, content_type) | 246 | data, attachment_name, content_type) |
130 | 236 | # pylint: enable=W0212 | 247 | # pylint: enable=W0212 |
131 | 237 | # all success record have the blobs added we return result of | 248 | # all success record have the blobs added we return result of |
132 | @@ -323,12 +334,15 @@ | |||
133 | 323 | """Delete record with given id""" | 334 | """Delete record with given id""" |
134 | 324 | record = self.get_record(record_id) | 335 | record = self.get_record(record_id) |
135 | 325 | new_record = copy.deepcopy(record) | 336 | new_record = copy.deepcopy(record) |
142 | 326 | dctrash = self.__class__( | 337 | if not self._dctrash: |
143 | 327 | database=DCTRASH, | 338 | self._dctrash = self.__class__( |
144 | 328 | uri=self.server_uri, | 339 | database=DCTRASH, |
145 | 329 | create=True, | 340 | uri=self.server_uri, |
146 | 330 | **self._server_class_extras) | 341 | create=True, |
147 | 331 | new_record.record_id = str(uuid.uuid4()) | 342 | **self._server_class_extras) |
148 | 343 | # pylint: disable=E1101 | ||
149 | 344 | new_record.record_id = base_n(uuid4().int, 62) | ||
150 | 345 | # pylint: enable=E1101 | ||
151 | 332 | del new_record._data['_rev'] | 346 | del new_record._data['_rev'] |
152 | 333 | try: | 347 | try: |
153 | 334 | del new_record._data['_attachments'] | 348 | del new_record._data['_attachments'] |
154 | @@ -339,7 +353,7 @@ | |||
155 | 339 | {'original_database_name': self._database_name, | 353 | {'original_database_name': self._database_name, |
156 | 340 | 'original_id': record_id}} | 354 | 'original_id': record_id}} |
157 | 341 | del self.db[record_id] | 355 | del self.db[record_id] |
159 | 342 | return dctrash.put_record(new_record) | 356 | return self._dctrash.put_record(new_record) |
160 | 343 | # pylint: enable=W0212 | 357 | # pylint: enable=W0212 |
161 | 344 | 358 | ||
162 | 345 | def delete_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT): | 359 | def delete_view(self, view_name, design_doc=DEFAULT_DESIGN_DOCUMENT): |
163 | @@ -362,7 +376,7 @@ | |||
164 | 362 | if len(view_container) > 0: | 376 | if len(view_container) > 0: |
165 | 363 | # Construct a new list of objects representing all views to have. | 377 | # Construct a new list of objects representing all views to have. |
166 | 364 | views = [ | 378 | views = [ |
168 | 365 | ViewDefinition(design_doc, k, v.get("map"), | 379 | self._views_factory(design_doc, k, v.get("map"), |
169 | 366 | v.get("reduce")) | 380 | v.get("reduce")) |
170 | 367 | for k, v in view_container.iteritems()] | 381 | for k, v in view_container.iteritems()] |
171 | 368 | # Push back a new batch of view. Pray to Eris that this doesn't | 382 | # Push back a new batch of view. Pray to Eris that this doesn't |
172 | @@ -372,7 +386,7 @@ | |||
173 | 372 | # its design-document from the ViewDefinition items, and if there | 386 | # its design-document from the ViewDefinition items, and if there |
174 | 373 | # are no items, then it has no idea of a design document to | 387 | # are no items, then it has no idea of a design document to |
175 | 374 | # update. This is a serious flaw. Thus, the "else" to follow. | 388 | # update. This is a serious flaw. Thus, the "else" to follow. |
177 | 375 | ViewDefinition.sync_many(self.db, views, remove_missing=True) | 389 | self._views_factory.sync_many(self.db, views, remove_missing=True) |
178 | 376 | else: | 390 | else: |
179 | 377 | # There are no views left in this design document. | 391 | # There are no views left in this design document. |
180 | 378 | 392 | ||
181 | @@ -401,7 +415,7 @@ | |||
182 | 401 | if design_doc is None: | 415 | if design_doc is None: |
183 | 402 | design_doc = view_name | 416 | design_doc = view_name |
184 | 403 | 417 | ||
186 | 404 | view = ViewDefinition(design_doc, view_name, map_js, reduce_js) | 418 | view = self._views_factory(design_doc, view_name, map_js, reduce_js) |
187 | 405 | view.sync(self.db) | 419 | view.sync(self.db) |
188 | 406 | assert self.view_exists(view_name, design_doc) | 420 | assert self.view_exists(view_name, design_doc) |
189 | 407 | 421 | ||
190 | 408 | 422 | ||
191 | === added file 'desktopcouch/records/tests/test_mocked_server.py' | |||
192 | --- desktopcouch/records/tests/test_mocked_server.py 1970-01-01 00:00:00 +0000 | |||
193 | +++ desktopcouch/records/tests/test_mocked_server.py 2011-01-05 17:11:33 +0000 | |||
194 | @@ -0,0 +1,519 @@ | |||
195 | 1 | # Copyright 2009-2010 Canonical Ltd. | ||
196 | 2 | # | ||
197 | 3 | # This file is part of desktopcouch. | ||
198 | 4 | # | ||
199 | 5 | # desktopcouch is free software: you can redistribute it and/or modify | ||
200 | 6 | # it under the terms of the GNU Lesser General Public License version 3 | ||
201 | 7 | # as published by the Free Software Foundation. | ||
202 | 8 | # | ||
203 | 9 | # desktopcouch is distributed in the hope that it will be useful, | ||
204 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
205 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
206 | 12 | # GNU Lesser General Public License for more details. | ||
207 | 13 | # | ||
208 | 14 | # You should have received a copy of the GNU Lesser General Public License | ||
209 | 15 | # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. | ||
210 | 16 | # | ||
211 | 17 | # Authors: Manuel de la Pena<manuel@canonical.com> | ||
212 | 18 | """Test the database code mocking the db access.""" | ||
213 | 19 | |||
214 | 20 | from couchdb.http import ResourceNotFound | ||
215 | 21 | from mocker import MockerTestCase, ANY, KWARGS | ||
216 | 22 | |||
217 | 23 | from desktopcouch.application.server import DesktopDatabase | ||
218 | 24 | from desktopcouch.records.database import NoSuchDatabase | ||
219 | 25 | |||
220 | 26 | |||
221 | 27 | # disable pylint warnings that are common due to the nature of the tests | ||
222 | 28 | # pylint: disable=W0104, W0201, W0212 | ||
223 | 29 | def set_database_expectations(testcase): | ||
224 | 30 | """Set the expectations for the db creation.""" | ||
225 | 31 | testcase.server_class(testcase.uri, ctx=testcase.ctx, | ||
226 | 32 | oauth_tokens=testcase.oauth_tokens) | ||
227 | 33 | testcase.mocker.result(testcase.server_class) | ||
228 | 34 | testcase.dbname in testcase.server_class | ||
229 | 35 | testcase.mocker.result(True) | ||
230 | 36 | testcase.server_class[testcase.dbname] | ||
231 | 37 | testcase.mocker.result(testcase.server_class) | ||
232 | 38 | testcase.server_class.info() | ||
233 | 39 | testcase.mocker.result({'update_seq': []}) | ||
234 | 40 | |||
235 | 41 | |||
236 | 42 | def set_create_view_expectations(testcase): | ||
237 | 43 | """Set the expectations for when a view is created.""" | ||
238 | 44 | # set expectations for the creation of the view | ||
239 | 45 | testcase.views_factory(ANY, ANY, ANY, ANY) | ||
240 | 46 | testcase.mocker.result(testcase.views_factory) | ||
241 | 47 | testcase.views_factory.sync(testcase.server_class) | ||
242 | 48 | # assert that is was added, shall we be mocking this? | ||
243 | 49 | testcase.server_class[ANY]['views'] | ||
244 | 50 | testcase.mocker.result(testcase.server_class) | ||
245 | 51 | ANY in testcase.server_class | ||
246 | 52 | testcase.mocker.result(True) | ||
247 | 53 | |||
248 | 54 | |||
249 | 55 | def create_database(testcase): | ||
250 | 56 | """Create a database to be used in the testcase.""" | ||
251 | 57 | testcase.database = DesktopDatabase(testcase.dbname, uri=testcase.uri, | ||
252 | 58 | record_factory=testcase.record_factory, create=True, | ||
253 | 59 | server_class=testcase.server_class, | ||
254 | 60 | oauth_tokens=testcase.oauth_tokens, ctx=testcase.ctx, | ||
255 | 61 | views_factory=testcase.views_factory) | ||
256 | 62 | |||
257 | 63 | |||
258 | 64 | class TestMockedCouchDatabaseDeprecated(MockerTestCase): | ||
259 | 65 | """Test the deprecated API.""" | ||
260 | 66 | |||
261 | 67 | def setUp(self): | ||
262 | 68 | """Setup each test.""" | ||
263 | 69 | super(TestMockedCouchDatabaseDeprecated, self).setUp() | ||
264 | 70 | # set mocked objects | ||
265 | 71 | self.record_factory = self.mocker.mock() | ||
266 | 72 | self.server_class = self.mocker.mock() | ||
267 | 73 | self.oauth_tokens = self.mocker.mock() | ||
268 | 74 | self.ctx = self.mocker.mock() | ||
269 | 75 | self.views_factory = self.mocker.mock() | ||
270 | 76 | # set not mocked required data | ||
271 | 77 | self.uri = 'uri' | ||
272 | 78 | self.dbname = self._testMethodName | ||
273 | 79 | self.database = None | ||
274 | 80 | |||
275 | 81 | def set_database_expectations(self): | ||
276 | 82 | """Set the expectations for the creation of the db.""" | ||
277 | 83 | self.server_class(self.uri, ctx=self.ctx, | ||
278 | 84 | oauth_tokens=self.oauth_tokens) | ||
279 | 85 | self.mocker.result(self.server_class) | ||
280 | 86 | self.dbname in self.server_class | ||
281 | 87 | self.mocker.result(True) | ||
282 | 88 | self.server_class[self.dbname] | ||
283 | 89 | self.mocker.result(self.server_class) | ||
284 | 90 | self.server_class.info() | ||
285 | 91 | self.mocker.result({'update_seq': []}) | ||
286 | 92 | |||
287 | 93 | def test_get_records_by_record_type_saved_view(self): | ||
288 | 94 | """Test getting multiple records by type.""" | ||
289 | 95 | record_type = 'test.com' | ||
290 | 96 | set_database_expectations(self) | ||
291 | 97 | # set expectations for the view_exists operation | ||
292 | 98 | self.server_class[ANY]['views'] | ||
293 | 99 | self.mocker.result(self.server_class) | ||
294 | 100 | ANY in self.server_class | ||
295 | 101 | self.mocker.result(True) | ||
296 | 102 | # set expectations for the execution | ||
297 | 103 | self.server_class.view(ANY, KWARGS) | ||
298 | 104 | self.mocker.result({record_type: record_type}) | ||
299 | 105 | self.mocker.replay() | ||
300 | 106 | create_database(self) | ||
301 | 107 | records = self.database.get_records(record_type=record_type, | ||
302 | 108 | create_view=True) | ||
303 | 109 | self.assertEqual(record_type, records) | ||
304 | 110 | |||
305 | 111 | def test_get_records_by_record_type_new_view(self): | ||
306 | 112 | """Test getting multiple record and create the view.""" | ||
307 | 113 | record_type = 'test.com' | ||
308 | 114 | set_database_expectations(self) | ||
309 | 115 | # set expectations for the view_exists operation | ||
310 | 116 | self.server_class[ANY]['views'] | ||
311 | 117 | self.mocker.result(self.server_class) | ||
312 | 118 | ANY in self.server_class | ||
313 | 119 | self.mocker.result(False) | ||
314 | 120 | set_create_view_expectations(self) | ||
315 | 121 | # set expectations for the execution | ||
316 | 122 | self.server_class.view(ANY, KWARGS) | ||
317 | 123 | self.mocker.result({record_type: record_type}) | ||
318 | 124 | self.mocker.replay() | ||
319 | 125 | create_database(self) | ||
320 | 126 | records = self.database.get_records(record_type=record_type, | ||
321 | 127 | create_view=True) | ||
322 | 128 | self.assertEqual(record_type, records) | ||
323 | 129 | |||
324 | 130 | def test_get_records_by_record_type_new_view_not_create(self): | ||
325 | 131 | """Test getting the records when the view does not exist.""" | ||
326 | 132 | record_type = 'test.com' | ||
327 | 133 | set_database_expectations(self) | ||
328 | 134 | # assert that is was added, shall we be mocking this? | ||
329 | 135 | self.server_class[ANY]['views'] | ||
330 | 136 | self.mocker.result(self.server_class) | ||
331 | 137 | ANY in self.server_class | ||
332 | 138 | self.mocker.result(True) | ||
333 | 139 | # set expectations for the execution | ||
334 | 140 | self.server_class.view(ANY, KWARGS) | ||
335 | 141 | self.mocker.result({record_type: record_type}) | ||
336 | 142 | self.mocker.replay() | ||
337 | 143 | create_database(self) | ||
338 | 144 | self.assertRaises(KeyError, self.database.get_records, | ||
339 | 145 | (record_type, False)) | ||
340 | 146 | |||
341 | 147 | def test_get_records_by_record_type_saved_view_false_create(self): | ||
342 | 148 | """Test calling the view and do not create it.""" | ||
343 | 149 | record_type = 'test.com' | ||
344 | 150 | set_database_expectations(self) | ||
345 | 151 | # set expectations for the view_exists operation | ||
346 | 152 | self.server_class[ANY]['views'] | ||
347 | 153 | self.mocker.result(self.server_class) | ||
348 | 154 | ANY in self.server_class | ||
349 | 155 | self.mocker.result(True) | ||
350 | 156 | # set expectations for the execution | ||
351 | 157 | self.server_class.view(ANY, KWARGS) | ||
352 | 158 | self.mocker.result({record_type: record_type}) | ||
353 | 159 | self.mocker.replay() | ||
354 | 160 | create_database(self) | ||
355 | 161 | records = self.database.get_records(record_type=record_type, | ||
356 | 162 | create_view=False) | ||
357 | 163 | self.assertEqual(record_type, records) | ||
358 | 164 | |||
359 | 165 | |||
360 | 166 | class TestMockedDesktopDatabase(MockerTestCase): | ||
361 | 167 | """Test case for DesktopDatabase.""" | ||
362 | 168 | |||
363 | 169 | def setUp(self): | ||
364 | 170 | """setup each test""" | ||
365 | 171 | super(TestMockedDesktopDatabase, self).setUp() | ||
366 | 172 | # set mocked objects | ||
367 | 173 | self.record_factory = self.mocker.mock() | ||
368 | 174 | self.server_class = self.mocker.mock() | ||
369 | 175 | self.oauth_tokens = self.mocker.mock() | ||
370 | 176 | self.ctx = self.mocker.mock() | ||
371 | 177 | self.views_factory = self.mocker.mock() | ||
372 | 178 | # set not mocked required data | ||
373 | 179 | self.uri = 'uri' | ||
374 | 180 | self.dbname = self._testMethodName | ||
375 | 181 | self.database = None | ||
376 | 182 | |||
377 | 183 | def _set_get_all_records_expectations(self, record_type, view_result, | ||
378 | 184 | records): | ||
379 | 185 | """Set the common expectations for the get_all_records call.""" | ||
380 | 186 | # replace the transform to records funtion which is used to | ||
381 | 187 | # return records instead of view results | ||
382 | 188 | transform_to_records = self.mocker.replace( | ||
383 | 189 | 'desktopcouch.records.database.transform_to_records') | ||
384 | 190 | self.server_class.view(ANY, KWARGS) | ||
385 | 191 | self.mocker.result(view_result) | ||
386 | 192 | transform_to_records(record_type) | ||
387 | 193 | self.mocker.result(records) | ||
388 | 194 | |||
389 | 195 | def test_database_not_exists(self): | ||
390 | 196 | """Test that the database does not exist.""" | ||
391 | 197 | # set expectations so that the server class returns that | ||
392 | 198 | # the db does ot exist. | ||
393 | 199 | self.server_class(self.uri, ctx=self.ctx, | ||
394 | 200 | oauth_tokens=self.oauth_tokens) | ||
395 | 201 | self.mocker.result(self.server_class) | ||
396 | 202 | self.dbname in self.server_class | ||
397 | 203 | self.mocker.result(False) | ||
398 | 204 | self.mocker.replay() | ||
399 | 205 | self.assertRaises(NoSuchDatabase, DesktopDatabase, self.dbname, | ||
400 | 206 | uri=self.uri, record_factory=self.record_factory, create=False, | ||
401 | 207 | server_class=self.server_class, oauth_tokens=self.oauth_tokens, | ||
402 | 208 | ctx=self.ctx, views_factory=self.views_factory) | ||
403 | 209 | |||
404 | 210 | def test_get_records_by_record_type_present_view(self): | ||
405 | 211 | """Test getting mutliple records by type with a present view.""" | ||
406 | 212 | record_type = 'test.com' | ||
407 | 213 | view_result = {record_type: record_type} | ||
408 | 214 | records = ['first', 'second'] | ||
409 | 215 | set_database_expectations(self) | ||
410 | 216 | self._set_get_all_records_expectations(record_type, view_result, | ||
411 | 217 | records) | ||
412 | 218 | self.mocker.replay() | ||
413 | 219 | create_database(self) | ||
414 | 220 | self.assertEqual(records, | ||
415 | 221 | self.database.get_all_records(record_type=record_type)) | ||
416 | 222 | |||
417 | 223 | def test_get_records_by_record_type_missing_view(self): | ||
418 | 224 | """Test getting mutliple records by type with a present view.""" | ||
419 | 225 | record_type = 'test.com' | ||
420 | 226 | view_result = {record_type: record_type} | ||
421 | 227 | records = ['first', 'second'] | ||
422 | 228 | set_database_expectations(self) | ||
423 | 229 | self.server_class.view(ANY, KWARGS) | ||
424 | 230 | # raise exception so that the view has to be created. | ||
425 | 231 | self.mocker.throw(ResourceNotFound) | ||
426 | 232 | set_create_view_expectations(self) | ||
427 | 233 | # set the expectations to be used after the creation of the view | ||
428 | 234 | self._set_get_all_records_expectations(record_type, view_result, | ||
429 | 235 | records) | ||
430 | 236 | self.mocker.replay() | ||
431 | 237 | create_database(self) | ||
432 | 238 | self.assertEqual(records, | ||
433 | 239 | self.database.get_all_records(record_type=record_type)) | ||
434 | 240 | |||
435 | 241 | def test_get_present_record_no_attachments(self): | ||
436 | 242 | """Test getting a record.""" | ||
437 | 243 | record_id = 'test_record' | ||
438 | 244 | data = {record_id: record_id} | ||
439 | 245 | record_mock = self.mocker.mock() | ||
440 | 246 | set_database_expectations(self) | ||
441 | 247 | self.server_class[record_id] | ||
442 | 248 | self.mocker.result(data) | ||
443 | 249 | self.record_factory(data=data) | ||
444 | 250 | self.mocker.result(record_mock) | ||
445 | 251 | self.mocker.replay() | ||
446 | 252 | create_database(self) | ||
447 | 253 | self.assertEqual(record_mock, self.database.get_record(record_id)) | ||
448 | 254 | |||
449 | 255 | def test_get_deleted_record(self): | ||
450 | 256 | """Test getting a deleted record.""" | ||
451 | 257 | record_id = 'random_id' | ||
452 | 258 | set_database_expectations(self) | ||
453 | 259 | self.server_class[record_id] | ||
454 | 260 | self.mocker.throw(ResourceNotFound) | ||
455 | 261 | self.mocker.replay() | ||
456 | 262 | create_database(self) | ||
457 | 263 | self.assertEqual(None, self.database.get_record(record_id)) | ||
458 | 264 | |||
459 | 265 | def test_put_record_no_attachments_or_id(self): | ||
460 | 266 | """Test putting a record with no attachments.""" | ||
461 | 267 | record_id = 'random_id' | ||
462 | 268 | record_mock = self.mocker.mock() | ||
463 | 269 | # replace the base_n funtion which is used to | ||
464 | 270 | # return the string representation of the uuid | ||
465 | 271 | base_n = self.mocker.replace( | ||
466 | 272 | 'desktopcouch.records.database.base_n') | ||
467 | 273 | set_database_expectations(self) | ||
468 | 274 | record_mock.record_id | ||
469 | 275 | self.mocker.result(None) | ||
470 | 276 | base_n(ANY, ANY) | ||
471 | 277 | self.mocker.result(record_id) | ||
472 | 278 | record_mock.record_id = record_id | ||
473 | 279 | record_mock._detached | ||
474 | 280 | self.mocker.result([]) | ||
475 | 281 | record_mock.list_attachments() | ||
476 | 282 | self.mocker.result([]) | ||
477 | 283 | record_mock._data | ||
478 | 284 | self.mocker.result({}) | ||
479 | 285 | self.server_class[record_id] = {} | ||
480 | 286 | self.mocker.replay() | ||
481 | 287 | create_database(self) | ||
482 | 288 | self.assertEqual(record_id, self.database.put_record(record_mock)) | ||
483 | 289 | |||
484 | 290 | def test_put_record_no_attachments(self): | ||
485 | 291 | """Test putting a record with no attachments.""" | ||
486 | 292 | record_id = 'random_id' | ||
487 | 293 | record_mock = self.mocker.mock() | ||
488 | 294 | set_database_expectations(self) | ||
489 | 295 | record_mock.record_id | ||
490 | 296 | self.mocker.result(record_id) | ||
491 | 297 | record_mock._detached | ||
492 | 298 | self.mocker.result([]) | ||
493 | 299 | record_mock.list_attachments() | ||
494 | 300 | self.mocker.result([]) | ||
495 | 301 | record_mock._data | ||
496 | 302 | self.mocker.result({}) | ||
497 | 303 | self.server_class[record_id] = {} | ||
498 | 304 | self.mocker.replay() | ||
499 | 305 | create_database(self) | ||
500 | 306 | self.assertEqual(record_id, self.database.put_record(record_mock)) | ||
501 | 307 | |||
502 | 308 | def test_put_record_with_attachments(self): | ||
503 | 309 | """Test putting a record with attachments.""" | ||
504 | 310 | record_id = 'random_id' | ||
505 | 311 | record_mock = self.mocker.mock() | ||
506 | 312 | set_database_expectations(self) | ||
507 | 313 | record_mock.record_id | ||
508 | 314 | self.mocker.result(record_id) | ||
509 | 315 | record_mock._data | ||
510 | 316 | self.mocker.result({}) | ||
511 | 317 | record_mock._detached | ||
512 | 318 | self.mocker.result(['to_delete']) | ||
513 | 319 | self.server_class.delete_attachment({}, 'to_delete') | ||
514 | 320 | record_mock.list_attachments() | ||
515 | 321 | self.mocker.result(['to_add']) | ||
516 | 322 | record_mock.attachment_data('to_add') | ||
517 | 323 | self.mocker.result(('data', 'type')) | ||
518 | 324 | self.server_class.put_attachment({}, 'data', 'to_add', 'type') | ||
519 | 325 | self.server_class[record_id] = {} | ||
520 | 326 | self.mocker.replay() | ||
521 | 327 | create_database(self) | ||
522 | 328 | self.assertEqual(record_id, self.database.put_record(record_mock)) | ||
523 | 329 | |||
524 | 330 | def test_put_records_batch(self): | ||
525 | 331 | """Test putting a batch of records.""" | ||
526 | 332 | records = {'1': (self.mocker.mock(), {'name': '1'}), | ||
527 | 333 | '2': (self.mocker.mock(), {'name': '2'})} | ||
528 | 334 | put_result = [] | ||
529 | 335 | for record_id in records: | ||
530 | 336 | put_result.append((True, record_id, record_id)) | ||
531 | 337 | set_database_expectations(self) | ||
532 | 338 | for record_id in records: | ||
533 | 339 | records[record_id][0].record_id | ||
534 | 340 | self.mocker.result(record_id) | ||
535 | 341 | # TODO: we currently call _data twice, we should get | ||
536 | 342 | # this number down | ||
537 | 343 | records[record_id][0]._data | ||
538 | 344 | self.mocker.result(records[record_id][1]) | ||
539 | 345 | records[record_id][0]._data | ||
540 | 346 | self.mocker.result(records[record_id][1]) | ||
541 | 347 | records[record_id][0]._detached | ||
542 | 348 | self.mocker.result(['to_delete']) | ||
543 | 349 | self.server_class.delete_attachment( | ||
544 | 350 | records[record_id][1], 'to_delete') | ||
545 | 351 | records[record_id][0].list_attachments() | ||
546 | 352 | self.mocker.result(['to_add']) | ||
547 | 353 | records[record_id][0].attachment_data('to_add') | ||
548 | 354 | self.mocker.result(('data', 'type')) | ||
549 | 355 | self.server_class.put_attachment({'_id': record_id, | ||
550 | 356 | '_rev': record_id}, 'data', 'to_add', 'type') | ||
551 | 357 | self.server_class.update([records[record_id][1] | ||
552 | 358 | for record_id in records]) | ||
553 | 359 | self.mocker.result(put_result) | ||
554 | 360 | self.mocker.replay() | ||
555 | 361 | create_database(self) | ||
556 | 362 | self.assertEqual(put_result, self.database.put_records_batch( | ||
557 | 363 | [records[record_id][0] for record_id in records])) | ||
558 | 364 | |||
559 | 365 | def test_delete_record_no_attachments(self): | ||
560 | 366 | """Test deleting a record with no attachments.""" | ||
561 | 367 | # replace the base_n funtion which is used to | ||
562 | 368 | # return the string representation of the uuid | ||
563 | 369 | base_n = self.mocker.replace( | ||
564 | 370 | 'desktopcouch.records.database.base_n') | ||
565 | 371 | # replace deepcopy because it is used to create a duplicate of | ||
566 | 372 | # the record | ||
567 | 373 | deepcopy = self.mocker.replace('copy.deepcopy') | ||
568 | 374 | record_id = 'test_record' | ||
569 | 375 | data = {record_id: record_id} | ||
570 | 376 | record_mock = self.mocker.mock() | ||
571 | 377 | trash_mock = self.mocker.mock() | ||
572 | 378 | set_database_expectations(self) | ||
573 | 379 | self.server_class[record_id] | ||
574 | 380 | self.mocker.result(data) | ||
575 | 381 | self.record_factory(data=data) | ||
576 | 382 | self.mocker.result(record_mock) | ||
577 | 383 | deepcopy(record_mock) | ||
578 | 384 | self.mocker.result(record_mock) | ||
579 | 385 | base_n(ANY, ANY) | ||
580 | 386 | self.mocker.result(record_id) | ||
581 | 387 | record_mock.record_id = record_id | ||
582 | 388 | del record_mock._data['_rev'] | ||
583 | 389 | del record_mock._data['_attachments'] | ||
584 | 390 | record_mock.application_annotations['desktopcouch'] = ANY | ||
585 | 391 | del self.server_class[record_id] | ||
586 | 392 | trash_mock.put_record(record_mock) | ||
587 | 393 | self.mocker.replay() | ||
588 | 394 | create_database(self) | ||
589 | 395 | self.database._dctrash = trash_mock | ||
590 | 396 | self.database.delete_record(record_id) | ||
591 | 397 | |||
592 | 398 | def test_record_exists(self): | ||
593 | 399 | """Test checking whether a record exists.""" | ||
594 | 400 | record_id = 'test_record' | ||
595 | 401 | set_database_expectations(self) | ||
596 | 402 | record_id in self.server_class | ||
597 | 403 | self.mocker.result(True) | ||
598 | 404 | record_id in self.server_class | ||
599 | 405 | self.mocker.result(False) | ||
600 | 406 | self.mocker.replay() | ||
601 | 407 | create_database(self) | ||
602 | 408 | self.assertTrue(self.database.record_exists(record_id)) | ||
603 | 409 | self.assertFalse(self.database.record_exists(record_id)) | ||
604 | 410 | |||
605 | 411 | def test_update_fields(self): | ||
606 | 412 | """Test the update_fields method.""" | ||
607 | 413 | dictionary = {'record_number': 0, 'field1': 1, 'field2': 2} | ||
608 | 414 | record_id = 'test_record' | ||
609 | 415 | set_database_expectations(self) | ||
610 | 416 | self.server_class[record_id] | ||
611 | 417 | self.mocker.result(dictionary) | ||
612 | 418 | self.server_class[record_id] = {'record_number': 0, 'field1': 11, | ||
613 | 419 | 'field2': 2} | ||
614 | 420 | self.mocker.replay() | ||
615 | 421 | create_database(self) | ||
616 | 422 | # no assertions are required because mocker will make sure that | ||
617 | 423 | # the new saved dict matches the expected one. | ||
618 | 424 | self.database.update_fields(record_id, {'field1': 11}) | ||
619 | 425 | |||
620 | 426 | def test_delete_not_present_view(self): | ||
621 | 427 | """Test deleting a view that is not in the db.""" | ||
622 | 428 | view_name = 'my_view' | ||
623 | 429 | design_doc = 'my_design' | ||
624 | 430 | doc_id = "_design/%s" % design_doc | ||
625 | 431 | set_database_expectations(self) | ||
626 | 432 | self.server_class[doc_id]['views'] | ||
627 | 433 | self.mocker.throw(ResourceNotFound) | ||
628 | 434 | self.mocker.replay() | ||
629 | 435 | create_database(self) | ||
630 | 436 | self.assertRaises(KeyError, self.database.delete_view, view_name, | ||
631 | 437 | design_doc) | ||
632 | 438 | |||
633 | 439 | def test_delete_present_view_not_last(self): | ||
634 | 440 | """Test deleting a view that is not the last in the doc.""" | ||
635 | 441 | view_name = 'my_view' | ||
636 | 442 | design_doc = 'my_design' | ||
637 | 443 | doc_id = "_design/%s" % design_doc | ||
638 | 444 | container_mock = self.mocker.mock() | ||
639 | 445 | key_mock = self.mocker.mock() | ||
640 | 446 | value_mock = self.mocker.mock() | ||
641 | 447 | iteritems = [(key_mock, value_mock)] | ||
642 | 448 | set_database_expectations(self) | ||
643 | 449 | self.server_class[doc_id]['views'] | ||
644 | 450 | self.mocker.result(container_mock) | ||
645 | 451 | container_mock.pop(view_name) | ||
646 | 452 | self.mocker.result(view_name) | ||
647 | 453 | len(container_mock) | ||
648 | 454 | self.mocker.result(3) | ||
649 | 455 | container_mock.iteritems() | ||
650 | 456 | self.mocker.result(iteritems) | ||
651 | 457 | value_mock.get('map') | ||
652 | 458 | self.mocker.result('map') | ||
653 | 459 | value_mock.get('reduce') | ||
654 | 460 | self.mocker.result('reduce') | ||
655 | 461 | self.views_factory(design_doc, key_mock, 'map', 'reduce') | ||
656 | 462 | self.mocker.result(container_mock) | ||
657 | 463 | self.views_factory.sync_many(ANY, ANY, remove_missing=True) | ||
658 | 464 | # set expectations for the view_exists operation | ||
659 | 465 | self.server_class[ANY]['views'] | ||
660 | 466 | self.mocker.result(self.server_class) | ||
661 | 467 | ANY in self.server_class | ||
662 | 468 | self.mocker.result(False) | ||
663 | 469 | self.mocker.replay() | ||
664 | 470 | create_database(self) | ||
665 | 471 | self.assertEqual(view_name, self.database.delete_view( | ||
666 | 472 | view_name, design_doc)) | ||
667 | 473 | |||
668 | 474 | def test_delete_present_view_is_last(self): | ||
669 | 475 | """Test deleting a view that is the last of the doc.""" | ||
670 | 476 | view_name = 'my_view' | ||
671 | 477 | design_doc = 'my_design' | ||
672 | 478 | doc_id = "_design/%s" % design_doc | ||
673 | 479 | container_mock = self.mocker.mock() | ||
674 | 480 | set_database_expectations(self) | ||
675 | 481 | self.server_class[doc_id]['views'] | ||
676 | 482 | self.mocker.result(container_mock) | ||
677 | 483 | container_mock.pop(view_name) | ||
678 | 484 | self.mocker.result(view_name) | ||
679 | 485 | len(container_mock) | ||
680 | 486 | self.mocker.result(0) | ||
681 | 487 | del self.server_class[doc_id] | ||
682 | 488 | # set expectations for the view_exists operation | ||
683 | 489 | self.server_class[ANY]['views'] | ||
684 | 490 | self.mocker.result(self.server_class) | ||
685 | 491 | ANY in self.server_class | ||
686 | 492 | self.mocker.result(False) | ||
687 | 493 | self.mocker.replay() | ||
688 | 494 | create_database(self) | ||
689 | 495 | self.assertEqual(view_name, self.database.delete_view( | ||
690 | 496 | view_name, design_doc)) | ||
691 | 497 | |||
692 | 498 | def test_list_views_no_design_doc(self): | ||
693 | 499 | """Test the list views when they do not exist..""" | ||
694 | 500 | design_doc = 'my_design' | ||
695 | 501 | doc_id = "_design/%s" % design_doc | ||
696 | 502 | set_database_expectations(self) | ||
697 | 503 | self.server_class[doc_id]['views'] | ||
698 | 504 | self.mocker.throw(KeyError) | ||
699 | 505 | self.mocker.replay() | ||
700 | 506 | create_database(self) | ||
701 | 507 | self.assertEqual([], self.database.list_views(design_doc)) | ||
702 | 508 | |||
703 | 509 | def test_list_views(self): | ||
704 | 510 | """Test the list views.""" | ||
705 | 511 | design_doc = 'my_design' | ||
706 | 512 | doc_id = "_design/%s" % design_doc | ||
707 | 513 | views = ['one', 'two', 'three'] | ||
708 | 514 | set_database_expectations(self) | ||
709 | 515 | self.server_class[doc_id]['views'] | ||
710 | 516 | self.mocker.result(views) | ||
711 | 517 | self.mocker.replay() | ||
712 | 518 | create_database(self) | ||
713 | 519 | self.assertEqual(views, self.database.list_views(design_doc)) | ||
714 | 0 | 520 | ||
715 | === modified file 'desktopcouch/records/tests/test_server.py' | |||
716 | --- desktopcouch/records/tests/test_server.py 2011-01-03 15:02:36 +0000 | |||
717 | +++ desktopcouch/records/tests/test_server.py 2011-01-05 17:11:33 +0000 | |||
718 | @@ -106,7 +106,7 @@ | |||
719 | 106 | break | 106 | break |
720 | 107 | 107 | ||
721 | 108 | def test_get_records_by_record_type_save_view(self): | 108 | def test_get_records_by_record_type_save_view(self): |
723 | 109 | """Test getting mutliple records by type""" | 109 | """Test getting multiple records by type""" |
724 | 110 | records = self.database.get_records( | 110 | records = self.database.get_records( |
725 | 111 | record_type="test.com", create_view=True) | 111 | record_type="test.com", create_view=True) |
726 | 112 | self.maybe_die() # should be able to survive couchdb death | 112 | self.maybe_die() # should be able to survive couchdb death |
I like it!