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