Merge lp:~bac/charmworld/bundles-display into lp:~juju-jitsu/charmworld/trunk
- bundles-display
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Brad Crittenden |
Approved revision: | 348 |
Merged at revision: | 344 |
Proposed branch: | lp:~bac/charmworld/bundles-display |
Merge into: | lp:~juju-jitsu/charmworld/trunk |
Diff against target: |
553 lines (+173/-74) 9 files modified
charmworld/jobs/ingest.py (+10/-8) charmworld/jobs/tests/test_ingest.py (+15/-11) charmworld/models.py (+50/-14) charmworld/search.py (+4/-3) charmworld/testing/factory.py (+19/-7) charmworld/tests/test_models.py (+19/-8) charmworld/tests/test_search.py (+35/-0) charmworld/views/api.py (+6/-5) charmworld/views/tests/test_api.py (+15/-18) |
To merge this branch: | bzr merge lp:~bac/charmworld/bundles-display |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Benji York (community) | Approve | ||
Review via email: mp+179541@code.launchpad.net |
Commit message
Change the basket field in the Bundle to be a basket name and revision number in the form 'basket/revno'.
Description of the change
Change the basket field in the Bundle to be a basket name and revision number in the form 'basket/revno'. Previously there was inconsistency in the way the basket id was created and used.
Remove from the top-level any bundle items that are in the 'data' dictionary.
Change models.
Brad Crittenden (bac) wrote : | # |
> The branch looks good. Here are some semi-bikesheddy comments.
>
> It's kind of strange that construct_basket_id returns two values while
> all the other "construct_*_id" functions just return the ID. Perhaps
> two functions would be better. (?)
I renamed to set_basket_info and modify the basket_data dictionary in place.
>
> Does "make lint" complain about the new unused variables?
Nope. Which ones?
>
>
> Re. get_bundle_data: so now if you don't specify an owner argument you
> get an empty string, but if you pass owner=None you get a random string?
No, both cases give you a random string.
> That seems a little odd. Oh, maybe you did that because the change in
> behavior broke a bunch of tests.
No, any broken tests were fixed.
>
> Another thing, about get_bundle_data: I always thought it was weird to
> use the local namespace as a sandbox in which to construct the return
> value. Now that you're doing more in the function and having to do the
> "del rep[k]" at the end it's getting more strained. You might consider
> revamping the function to use **charm_data as the parameter list instead
> and then manipulating charm_data instead of the locals.
I've fixed it to be more explicit.
Thanks for the review and good suggestions.
Charmworld Lander (charmworld-lander) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'charmworld/jobs/ingest.py' |
2 | --- charmworld/jobs/ingest.py 2013-08-09 18:53:40 +0000 |
3 | +++ charmworld/jobs/ingest.py 2013-08-11 23:10:56 +0000 |
4 | @@ -288,13 +288,14 @@ |
5 | self.working_dir = working_dir |
6 | super(UpdateBundleJob, self).__init__() |
7 | |
8 | - def store_bundles(self, deployer_config, basket_id): |
9 | - store_bundles(self.db.bundles, deployer_config, basket_id) |
10 | + def store_bundles(self, deployer_config, owner, basket_id): |
11 | + store_bundles(self.db.bundles, deployer_config, owner, basket_id) |
12 | |
13 | @staticmethod |
14 | - def construct_basket_id(basket_data, revno): |
15 | - owner_segment = '~%s/' % basket_data['owner'] |
16 | - return '%s%s-%d' % (owner_segment, basket_data['name'], revno) |
17 | + def set_basket_info(data, revno): |
18 | + owner_segment = '~%s' % data['owner'] |
19 | + data['name_revno'] = "%s/%d" % (data['name'], revno) |
20 | + data['_id'] = '%s/%s' % (owner_segment, data['name_revno']) |
21 | |
22 | @staticmethod |
23 | def decorate_bundles(basket_contents, basket_id): |
24 | @@ -306,7 +307,7 @@ |
25 | branch_dir = fetch_branch(self.working_dir, basket_data, self.log) |
26 | branch = Branch.open(branch_dir) |
27 | revno = branch.revision_id_to_revno(basket_data['commit']) |
28 | - basket_data['_id'] = self.construct_basket_id(basket_data, revno) |
29 | + self.set_basket_info(basket_data, revno) |
30 | with read_locked(branch): |
31 | tree = branch.repository.revision_tree(basket_data['commit']) |
32 | basket_data['file_hashes'] = quote_yaml(slurp_files(fs, tree)) |
33 | @@ -324,7 +325,8 @@ |
34 | self.db.baskets.save(basket_data) |
35 | deployer_config = self.get_deployer_config(fs, basket_data) |
36 | self.decorate_bundles(deployer_config, basket_data['_id']) |
37 | - self.store_bundles(deployer_config, basket_data['_id']) |
38 | + self.store_bundles( |
39 | + deployer_config, basket_data['owner'], basket_data['name_revno']) |
40 | |
41 | |
42 | def _rev_info(r, branch): |
43 | @@ -333,7 +335,7 @@ |
44 | "revno": branch.revision_id_to_revno(r.revision_id), |
45 | "committer": r.committer, |
46 | "created": r.timestamp, |
47 | - "message": r.message |
48 | + "message": r.message, |
49 | } |
50 | return d |
51 | |
52 | |
53 | === modified file 'charmworld/jobs/tests/test_ingest.py' |
54 | --- charmworld/jobs/tests/test_ingest.py 2013-08-09 18:53:40 +0000 |
55 | +++ charmworld/jobs/tests/test_ingest.py 2013-08-11 23:10:56 +0000 |
56 | @@ -383,7 +383,7 @@ |
57 | with bundle_update_environment(source_branch_dir): |
58 | job.decorate_basket(bundle_data, None) |
59 | |
60 | - self.assertTrue(bundle_data['_id'].endswith('-8')) |
61 | + self.assertTrue(bundle_data['_id'].endswith('/8')) |
62 | |
63 | def test_decorate_basket_stores_files(self): |
64 | job = UpdateBundleJob() |
65 | @@ -423,21 +423,22 @@ |
66 | with self.assertRaises(bzrlib.errors.NoSuchRevision): |
67 | job.decorate_basket(bundle_data, None) |
68 | |
69 | - def test_construct_basket_id_includes_owner_if_promulgated(self): |
70 | + def test_set_basket_info_includes_owner_if_promulgated(self): |
71 | payload = factory.get_payload_json(name='mysql', promulgated=True) |
72 | - basket_id = UpdateBundleJob.construct_basket_id(payload, 5) |
73 | - self.assertEqual('~charmers/mysql-5', basket_id) |
74 | + UpdateBundleJob.set_basket_info(payload, 5) |
75 | + self.assertEqual('mysql/5', payload['name_revno']) |
76 | + self.assertEqual('~charmers/mysql/5', payload['_id']) |
77 | |
78 | - def test_construct_basket_id_includes_owner_if_not_promulgated(self): |
79 | + def test_set_basket_info_includes_owner_if_not_promulgated(self): |
80 | payload = factory.get_payload_json(name='mysql', promulgated=False) |
81 | - basket_id = UpdateBundleJob.construct_basket_id(payload, 5) |
82 | - self.assertEqual('~charmers/mysql-5', basket_id) |
83 | + UpdateBundleJob.set_basket_info(payload, 5) |
84 | + self.assertEqual('mysql/5', payload['name_revno']) |
85 | + self.assertEqual('~charmers/mysql/5', payload['_id']) |
86 | |
87 | def test_job_can_store_bundles_in_the_db(self): |
88 | basket_data = factory.get_payload_json(name='wordpress') |
89 | - basket_id = UpdateBundleJob.construct_basket_id(basket_data, 5) |
90 | - bundle_id = '%s/%s' % (basket_id, 'stage') |
91 | - basket_data['_id'] = basket_id |
92 | + UpdateBundleJob.set_basket_info(basket_data, 5) |
93 | + bundle_id = '%s/%s' % (basket_data['_id'], 'stage') |
94 | job = UpdateBundleJob() |
95 | job.setup(self.db) |
96 | DEPLOYER_CONFIG_HASH = '31337' |
97 | @@ -457,11 +458,14 @@ |
98 | engine: apache |
99 | """) |
100 | fs.put(deployer_config, _id=DEPLOYER_CONFIG_HASH) |
101 | - job.store_bundles(yaml.safe_load(deployer_config), basket_id) |
102 | + job.store_bundles( |
103 | + yaml.safe_load(deployer_config), |
104 | + basket_data['owner'], basket_data['name_revno']) |
105 | self.assertIsNotNone(self.db.bundles.find_one(bundle_id)) |
106 | |
107 | def test_job_run_stores_bundles_in_the_db(self): |
108 | basket_data = factory.get_payload_json(name='wordpress') |
109 | + basket_data['name_revno'] = 'dummy' |
110 | job = UpdateBundleJob() |
111 | job.setup(self.db) |
112 | with patch.object(job, 'get_deployer_config'): |
113 | |
114 | === modified file 'charmworld/models.py' |
115 | --- charmworld/models.py 2013-08-09 18:53:40 +0000 |
116 | +++ charmworld/models.py 2013-08-11 23:10:56 +0000 |
117 | @@ -7,14 +7,17 @@ |
118 | from bzrlib.branch import Branch |
119 | from bzrlib.errors import NoSuchRevision |
120 | from calendar import timegm |
121 | +import copy |
122 | import hashlib |
123 | import logging |
124 | from mimetypes import guess_type |
125 | import os |
126 | -from os.path import dirname |
127 | -from os.path import join |
128 | -from os.path import normpath |
129 | -from os.path import split |
130 | +from os.path import ( |
131 | + dirname, |
132 | + join, |
133 | + normpath, |
134 | + split, |
135 | +) |
136 | import random |
137 | import re |
138 | |
139 | @@ -1176,11 +1179,9 @@ |
140 | 'basket': None, |
141 | 'name': '', |
142 | 'inherits': None, |
143 | - 'series': '', |
144 | 'title': '', |
145 | 'description': '', |
146 | - 'services': dict(), |
147 | - 'relations': dict(), |
148 | + 'data': dict(), |
149 | 'promulgated': False, |
150 | 'branch_deleted': False, |
151 | 'doctype': 'bundle', |
152 | @@ -1204,9 +1205,13 @@ |
153 | else: |
154 | raise AttributeError |
155 | |
156 | + @staticmethod |
157 | + def construct_id(owner, basket, name): |
158 | + return "~%s/%s/%s" % (owner, basket, name) |
159 | + |
160 | @property |
161 | def id(self): |
162 | - return "~{}/{}/{}".format(self.owner, self.basket, self.name) |
163 | + return self.construct_id(self.owner, self.basket, self.name) |
164 | |
165 | @property |
166 | def permanent_url(self): |
167 | @@ -1303,14 +1308,45 @@ |
168 | return options |
169 | |
170 | |
171 | -def store_bundles(collection, deployer_config, basket_id): |
172 | - """Store a basket of bundles into both MongoDB and ElasticSearch.""" |
173 | - index_client = ElasticSearchClient.from_settings(settings) |
174 | - index_client.index_bundles(deployer_config.values()) |
175 | +def store_bundles(collection, deployer_config, owner, basket_id, |
176 | + index_client=None): |
177 | + """Store a basket of bundles into MongoDB and/or ElasticSearch. |
178 | + |
179 | + :param: collection: A db bundles collection. If None the bundles are not |
180 | + stored to Mongo. |
181 | + :param: deployer_config: A dictionary representing the basket of bundles. |
182 | + :param: owner: A string representing the owner. No leading ~. |
183 | + :param: basket_id: A slash-joined string of name and revision for the |
184 | + basket. Does not include the owner name. |
185 | + :param: index_client: The elastic search client. If None it will be |
186 | + retrieved. Useful for Mongo-only tests where the indexing is not |
187 | + needed. |
188 | + """ |
189 | + |
190 | + if index_client is None: |
191 | + index_client = ElasticSearchClient.from_settings(settings) |
192 | + |
193 | + # Augment the deployer_config with additional information for indexing. |
194 | + ids = {} |
195 | + config_copy = copy.deepcopy(deployer_config) |
196 | + for key in config_copy: |
197 | + ids[key] = Bundle.construct_id(owner, basket_id, key) |
198 | + config_copy[key]['id'] = ids[key] |
199 | + config_copy[key]['owner'] = owner |
200 | + config_copy[key]['name'] = key |
201 | + config_copy[key]['basket'] = basket_id |
202 | + |
203 | + index_client.index_bundles(config_copy.values()) |
204 | + if collection is None: |
205 | + return |
206 | for key in deployer_config: |
207 | + data = get_flattened_deployment(deployer_config, key) |
208 | bundle_doc = { |
209 | - '_id': '%s/%s' % (basket_id, key), |
210 | - 'data': get_flattened_deployment(deployer_config, key), |
211 | + '_id': ids[key], |
212 | + 'name': key, |
213 | + 'owner': owner, |
214 | + 'basket': basket_id, |
215 | + 'data': data, |
216 | } |
217 | collection.save(bundle_doc) |
218 | |
219 | |
220 | === modified file 'charmworld/search.py' |
221 | --- charmworld/search.py 2013-08-09 18:53:40 +0000 |
222 | +++ charmworld/search.py 2013-08-11 23:10:56 +0000 |
223 | @@ -32,6 +32,7 @@ |
224 | 'description': 3, |
225 | 'config.options.description': None, |
226 | 'relations': None, |
227 | + 'services': None, |
228 | } |
229 | |
230 | |
231 | @@ -430,9 +431,9 @@ |
232 | |
233 | with Timer() as timer: |
234 | status = self.get_status() |
235 | - for real_index in self.get_aliased(): |
236 | - break |
237 | - else: |
238 | + try: |
239 | + real_index = self.get_aliased()[0] |
240 | + except IndexError: |
241 | real_index = self.index_name |
242 | docs = status[real_index].get('docs') |
243 | if docs is None: |
244 | |
245 | === modified file 'charmworld/testing/factory.py' |
246 | --- charmworld/testing/factory.py 2013-08-09 18:53:40 +0000 |
247 | +++ charmworld/testing/factory.py 2013-08-11 23:10:56 +0000 |
248 | @@ -11,7 +11,6 @@ |
249 | ) |
250 | import hashlib |
251 | import os |
252 | -from os.path import basename |
253 | from random import randint |
254 | import random |
255 | import stat |
256 | @@ -311,17 +310,29 @@ |
257 | return record_id, charm |
258 | |
259 | |
260 | -def get_bundle_data(name=None, owner='', basket=None, inherits=None, |
261 | +def get_bundle_data(name=None, owner=None, basket=None, inherits=None, |
262 | series='precise', title='', description='', |
263 | services=dict(), relations=dict(), promulgated=False, |
264 | branch_deleted=False): |
265 | if name is None: |
266 | name = random_string(10) |
267 | + if owner is None: |
268 | + owner = random_string(10) |
269 | if basket is None: |
270 | - basket = random_string(10) |
271 | - doctype = 'bundle' |
272 | - doctype # Hush lint's dislike of defining variables for the vars() call. |
273 | - return dict(**vars()) |
274 | + basket = "%s/%d" % (random_string(10), randint(0, 100)) |
275 | + data = dict(series=series, |
276 | + relations=relations, |
277 | + services=services) |
278 | + return dict(basket=basket, |
279 | + branch_deleted=branch_deleted, |
280 | + data=data, |
281 | + description=description, |
282 | + doctype='bundle', |
283 | + inherits=inherits, |
284 | + name=name, |
285 | + owner=owner, |
286 | + promulgated=promulgated, |
287 | + title=title) |
288 | |
289 | |
290 | def makeBundle(db, *args, **kwargs): |
291 | @@ -334,7 +345,8 @@ |
292 | |
293 | def make_charm_file(db, charm, path, content=None): |
294 | fs = getfs(db) |
295 | - charm_file = CharmFileSet.make_charm_file(fs, charm, path, basename(path)) |
296 | + charm_file = CharmFileSet.make_charm_file( |
297 | + fs, charm, path, os.path.basename(path)) |
298 | if content is None: |
299 | content = random_string(10).encode('utf-8') |
300 | charm_file.save(content) |
301 | |
302 | === modified file 'charmworld/tests/test_models.py' |
303 | --- charmworld/tests/test_models.py 2013-08-09 18:53:40 +0000 |
304 | +++ charmworld/tests/test_models.py 2013-08-11 23:10:56 +0000 |
305 | @@ -1458,6 +1458,7 @@ |
306 | |
307 | def test_store_bundles(self): |
308 | # The bundles can be stored in the database. |
309 | + |
310 | deployer_config = dedent("""\ |
311 | wordpress-stage: |
312 | series: precise |
313 | @@ -1470,13 +1471,21 @@ |
314 | engine: apache |
315 | """) |
316 | parsed = yaml.safe_load(deployer_config) |
317 | - store_bundles(self.db.bundles, parsed, 'wordpress-5') |
318 | + owner = 'bac' |
319 | + bundle_name = 'wordpress-stage' |
320 | + basket_id = 'wordpress-basket/5' |
321 | + _id = "~%s/%s/%s" % (owner, basket_id, bundle_name) |
322 | + store_bundles( |
323 | + self.db.bundles, parsed, 'bac', basket_id) |
324 | self.assertEqual( |
325 | { |
326 | - '_id': 'wordpress-5/wordpress-stage', |
327 | + '_id': _id, |
328 | + 'basket': basket_id, |
329 | + 'name': bundle_name, |
330 | + 'owner': owner, |
331 | 'data': parsed['wordpress-stage'], |
332 | }, |
333 | - self.db.bundles.find_one('wordpress-5/wordpress-stage')) |
334 | + self.db.bundles.find_one(_id)) |
335 | |
336 | def test_store_bundles_calls_get_flattened_deployment(self): |
337 | # For every key in the top-level deployment data, |
338 | @@ -1509,7 +1518,7 @@ |
339 | |
340 | with patch('charmworld.models.get_flattened_deployment', |
341 | get_flattened_deployment): |
342 | - store_bundles(self.db.bundles, parsed, 'wordpress-5') |
343 | + store_bundles(self.db.bundles, parsed, 'bac', 'wordpress-basket/5') |
344 | self.assertItemsEqual(['wordpress-stage', 'wordpress-prod'], keys) |
345 | |
346 | def test_storing_a_bundle_includes_indexing_it(self): |
347 | @@ -1527,7 +1536,7 @@ |
348 | with patch( |
349 | 'charmworld.models.ElasticSearchClient', |
350 | FauxElasticSearchClient): |
351 | - store_bundles(self.db.bundles, {}, 'wordpress-5') |
352 | + store_bundles(self.db.bundles, {}, 'owner', 'wordpress-basket/5') |
353 | |
354 | self.assertTrue(FauxElasticSearchClient.index_bundles_called) |
355 | |
356 | @@ -1547,12 +1556,14 @@ |
357 | 'basket': 'mysql', |
358 | 'name': 'tiny', |
359 | 'inherits': 'main-mysql', |
360 | - 'series': 'series', |
361 | 'title': 'Tiny bundle', |
362 | 'description': 'My Tiny Bundle', |
363 | 'doctype': 'bundle', |
364 | - 'services': dict(apache=1), |
365 | - 'relations': dict(a=1), |
366 | + 'data': { |
367 | + 'services': dict(apache=1), |
368 | + 'relations': dict(a=1), |
369 | + 'series': 'series', |
370 | + }, |
371 | 'promulgated': True, |
372 | 'branch_deleted': True, |
373 | } |
374 | |
375 | === modified file 'charmworld/tests/test_search.py' |
376 | --- charmworld/tests/test_search.py 2013-08-09 18:53:40 +0000 |
377 | +++ charmworld/tests/test_search.py 2013-08-11 23:10:56 +0000 |
378 | @@ -5,12 +5,14 @@ |
379 | |
380 | |
381 | from datetime import datetime |
382 | +from textwrap import dedent |
383 | |
384 | from pyelasticsearch import ElasticSearch |
385 | from pyelasticsearch.exceptions import ( |
386 | ElasticHttpError, |
387 | ElasticHttpNotFoundError, |
388 | ) |
389 | +import yaml |
390 | |
391 | from charmworld.charmstore import ( |
392 | get_address, |
393 | @@ -19,6 +21,7 @@ |
394 | from charmworld.models import ( |
395 | Bundle, |
396 | Charm, |
397 | + store_bundles, |
398 | ) |
399 | from charmworld.search import ( |
400 | BUNDLE, |
401 | @@ -146,6 +149,38 @@ |
402 | self.assertEqual(1, len(results)) |
403 | self.assertEqual(bundle, results[0]['data']) |
404 | |
405 | + def test_store_bundles(self): |
406 | + # Ensure that bundles stored into the index via the `store_bundles` |
407 | + # function are searchable via the index. |
408 | + _id = '~abentley/wordpress-basket/5/wordpress-stage' |
409 | + deployer_config = dedent("""\ |
410 | + wordpress-stage: |
411 | + series: precise |
412 | + services: |
413 | + blog: |
414 | + charm: cs:precise/wordpress |
415 | + constraints: mem=2 |
416 | + options: |
417 | + tuning: optimized |
418 | + engine: apache |
419 | + """) |
420 | + parsed = yaml.safe_load(deployer_config) |
421 | + client = self.index_client |
422 | + store_bundles(None, parsed, 'abentley', 'wordpress-basket/5', |
423 | + index_client=client) |
424 | + # Bundle name is indexed. |
425 | + results = client.search('wordpress-stage')['results']['bundle'] |
426 | + self.assertEqual(1, len(results)) |
427 | + self.assertEqual(_id, results[0]['data'].id) |
428 | + # Series is indexed. |
429 | + results = client.search('precise')['results']['bundle'] |
430 | + self.assertEqual(1, len(results)) |
431 | + self.assertEqual(_id, results[0]['data'].id) |
432 | + # Owner is indexed. |
433 | + results = client.search('abentley')['results']['bundle'] |
434 | + self.assertEqual(1, len(results)) |
435 | + self.assertEqual(_id, results[0]['data'].id) |
436 | + |
437 | def test_search_charms_and_bundles_same_name(self): |
438 | charm = Charm(self.makeCharm(name='mozilla')) |
439 | bundle = Bundle(self.makeBundle(name='mozilla')) |
440 | |
441 | === modified file 'charmworld/views/api.py' |
442 | --- charmworld/views/api.py 2013-08-07 13:31:20 +0000 |
443 | +++ charmworld/views/api.py 2013-08-11 23:10:56 +0000 |
444 | @@ -351,11 +351,12 @@ |
445 | if path is None: |
446 | raise HTTPNotFound(self.request.path) |
447 | fullpath = '/'.join(path) |
448 | - if len(path) == 3: |
449 | + if len(path) == 4: |
450 | # We have an owner. |
451 | query = {'_id': fullpath} |
452 | - elif len(path) == 2: |
453 | - query = {'basket': path[0], 'name': path[1], 'promulgated': True} |
454 | + elif len(path) == 3: |
455 | + basket_id = join(path[0], path[1]) |
456 | + query = {'basket': basket_id, 'name': path[2], 'promulgated': True} |
457 | else: |
458 | raise HTTPNotFound(self.request.path) |
459 | bundle_data = self.request.db.bundles.find_one(query) |
460 | @@ -363,7 +364,7 @@ |
461 | return json_response( |
462 | 404, {'type': 'no_such_bundle', 'bundle_id': fullpath}) |
463 | bundle = Bundle(bundle_data) |
464 | - return bundle._representation # wrong |
465 | + return {bundle.name: bundle.data} |
466 | |
467 | @staticmethod |
468 | def _get_file_headers(md5sum, content_type=None): |
469 | @@ -371,7 +372,7 @@ |
470 | ("Access-Control-Allow-Origin", "*"), |
471 | ("Access-Control-Allow-Headers", "X-Requested-With"), |
472 | ("Cache-Control", "max-age=86400, public"), |
473 | - ("Etag", '"%s"' % md5sum) |
474 | + ("Etag", '"%s"' % md5sum), |
475 | ] |
476 | if content_type is not None: |
477 | headerlist.append(('Content-Type', content_type)) |
478 | |
479 | === modified file 'charmworld/views/tests/test_api.py' |
480 | --- charmworld/views/tests/test_api.py 2013-08-09 15:46:51 +0000 |
481 | +++ charmworld/views/tests/test_api.py 2013-08-11 23:10:56 +0000 |
482 | @@ -635,15 +635,16 @@ |
483 | bundle = self.makeBundle() |
484 | expected = dict( |
485 | name=bundle.name, |
486 | - owner='', |
487 | + owner=bundle.owner, |
488 | basket=bundle.basket, |
489 | inherits=None, |
490 | - series='precise', |
491 | title='', |
492 | + data=dict(services=dict(), |
493 | + relations=dict(), |
494 | + series='precise', |
495 | + ), |
496 | description='', |
497 | doctype='bundle', |
498 | - services=dict(), |
499 | - relations=dict(), |
500 | promulgated=False, |
501 | branch_deleted=False, |
502 | ) |
503 | @@ -667,7 +668,7 @@ |
504 | |
505 | def test_not_found(self): |
506 | self.makeBundle(name='bat') |
507 | - response = self.get_response('bundle', 'foo/bar') |
508 | + response = self.get_response('bundle', 'foo/4/bar') |
509 | self.assertEqual(404, response.status_code) |
510 | |
511 | def test_no_path(self): |
512 | @@ -675,32 +676,28 @@ |
513 | self.get_response('bundle', remainder=None) |
514 | |
515 | def test_valid_lookup_unpromulgated(self): |
516 | - self.makeBundle(name='bat', owner='bac', basket='byobu-4') |
517 | - response = self.get_response('bundle', '~bac/byobu-4/bat') |
518 | + self.makeBundle(name='bat', owner='bac', basket='byobu/4') |
519 | + response = self.get_response('bundle', '~bac/byobu/4/bat') |
520 | self.assertEqual(200, response.status_code) |
521 | - response = self.get_response('bundle', 'byobu-4/bat') |
522 | + response = self.get_response('bundle', 'byobu/4/bat') |
523 | self.assertEqual(404, response.status_code) |
524 | |
525 | def test_valid_lookup_promulgated(self): |
526 | self.makeBundle( |
527 | - name='bat', owner='bac', basket='byobu-4', |
528 | + name='bat', owner='bac', basket='byobu/4', |
529 | promulgated=True) |
530 | # Can look up via owner-based id and short id. |
531 | - response = self.get_response('bundle', '~bac/byobu-4/bat') |
532 | + response = self.get_response('bundle', '~bac/byobu/4/bat') |
533 | self.assertEqual(200, response.status_code) |
534 | - response = self.get_response('bundle', 'byobu-4/bat') |
535 | + response = self.get_response('bundle', 'byobu/4/bat') |
536 | self.assertEqual(200, response.status_code) |
537 | |
538 | def test_results_match(self): |
539 | - self.maxDiff = None |
540 | - bundle = self.makeBundle(name='bat', owner='bac', basket='byobu-4') |
541 | - response = self.get_response('bundle', '~bac/byobu-4/bat') |
542 | + bundle = self.makeBundle(name='bat', owner='bac', basket='byobu/4') |
543 | + response = self.get_response('bundle', '~bac/byobu/4/bat') |
544 | self.assertEqual(200, response.status_code) |
545 | - self.assertEqual(bundle.id, response.json['_id']) |
546 | - # Copy the json dict so we can modify it. |
547 | json = dict(response.json) |
548 | - del json['_id'] |
549 | - self.assertEqual(json, bundle._representation) |
550 | + self.assertEqual(json['bat'], bundle.data) |
551 | |
552 | |
553 | class TestAPI2Bundles(TestAPIBundles, API2Mixin): |
The branch looks good. Here are some semi-bikesheddy comments.
It's kind of strange that construct_basket_id returns two values while
all the other "construct_*_id" functions just return the ID. Perhaps
two functions would be better. (?)
Does "make lint" complain about the new unused variables?
Re. get_bundle_data: so now if you don't specify an owner argument you
get an empty string, but if you pass owner=None you get a random string?
That seems a little odd. Oh, maybe you did that because the change in
behavior broke a bunch of tests.
Another thing, about get_bundle_data: I always thought it was weird to
use the local namespace as a sandbox in which to construct the return
value. Now that you're doing more in the function and having to do the
"del rep[k]" at the end it's getting more strained. You might consider
revamping the function to use **charm_data as the parameter list instead
and then manipulating charm_data instead of the locals.