Merge lp:~makyo/charms/trusty/juju-gui/guiserver-v4bundle into lp:charms/trusty/juju-gui
- Trusty Tahr (14.04)
- guiserver-v4bundle
- Merge into trunk
Proposed by
Madison Scott-Clary
on 2015-02-10
| Status: | Merged |
|---|---|
| Merged at revision: | 110 |
| Proposed branch: | lp:~makyo/charms/trusty/juju-gui/guiserver-v4bundle |
| Merge into: | lp:charms/trusty/juju-gui |
| Diff against target: |
513 lines (+274/-27) 6 files modified
server/guiserver/bundles/__init__.py (+23/-7) server/guiserver/bundles/base.py (+4/-3) server/guiserver/bundles/views.py (+17/-4) server/guiserver/tests/bundles/test_base.py (+81/-7) server/guiserver/tests/bundles/test_views.py (+140/-4) server/guiserver/tests/helpers.py (+9/-2) |
| To merge this branch: | bzr merge lp:~makyo/charms/trusty/juju-gui/guiserver-v4bundle |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Juju GUI Charmers | 2015-02-10 | Pending | |
|
Review via email:
|
|||
Commit Message
Description of the Change
Adds support for v4 (basketless) bundles as used by the charmstore. This retains legacy v3 functionality for the time being.
To post a comment you must log in.
| Francesco Banconi (frankban) wrote : | # |
lp:~makyo/charms/trusty/juju-gui/guiserver-v4bundle
updated
on 2015-02-17
- 113. By Madison Scott-Clary on 2015-02-12
-
Reduce code repetition by adding a version to the params
- 114. By Madison Scott-Clary on 2015-02-13
-
Reduce repetition in views
- 115. By Madison Scott-Clary on 2015-02-17
-
Update docs to refer to v4 bundles
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'server/guiserver/bundles/__init__.py' |
| 2 | --- server/guiserver/bundles/__init__.py 2013-11-15 12:41:59 +0000 |
| 3 | +++ server/guiserver/bundles/__init__.py 2015-02-17 17:42:45 +0000 |
| 4 | @@ -30,7 +30,9 @@ |
| 5 | The following arguments are passed to the validate and import_bundle |
| 6 | interface methods: |
| 7 | - user: a guiserver.auth.User instance, representing a logged in user; |
| 8 | - - name: a string representing the name of the bundle to be imported; |
| 9 | + - name: a string representing the name of the bundle to be imported; in |
| 10 | + the case of v3 bundles, this will be the name of the bundle within |
| 11 | + the basket, in v4 bundles, this will be the bundle ID; |
| 12 | - bundle: a YAML decoded object representing the bundle contents. |
| 13 | The watch and next interface methods are used to retrieve information |
| 14 | about the status of the currently started/scheduled deployments. |
| 15 | @@ -87,7 +89,7 @@ |
| 16 | Importing a bundle. |
| 17 | ------------------- |
| 18 | |
| 19 | -A deployment request looks like the following: |
| 20 | +A v3 deployment request looks like the following: |
| 21 | |
| 22 | { |
| 23 | 'RequestId': 1, |
| 24 | @@ -100,12 +102,26 @@ |
| 25 | }, |
| 26 | } |
| 27 | |
| 28 | +A v4 deployment request looks like the following: |
| 29 | + |
| 30 | + { |
| 31 | + 'RequestId': 1, |
| 32 | + 'Type': 'Deployer', |
| 33 | + 'Request': 'Import', |
| 34 | + 'Params': { |
| 35 | + 'Version': 4, |
| 36 | + 'YAML': 'bundle', |
| 37 | + 'BundleID': 'id' |
| 38 | + }, |
| 39 | + } |
| 40 | + |
| 41 | In the request parameters above, the YAML field stores the YAML encoded |
| 42 | -contents representing one or more bundles, and the Name field is the name of |
| 43 | -the specific bundle (included in YAML) that must be deployed. The Name |
| 44 | -parameter is optional in the case YAML includes only one bundle. The BundleID |
| 45 | -is optional and is used for incrementing the deployment counter in |
| 46 | -Charmworld. |
| 47 | +contents representing one (or more, in the case of v3 baskets) bundles, and the |
| 48 | +Name field is the name of the specific bundle (included in YAML) that must be |
| 49 | +deployed. The Name parameter is optional in the case YAML includes only one |
| 50 | +bundle, or in the case of v4 bundles. The BundleID is optional for v3 bundles |
| 51 | +and required for v4 bundles, and is used for incrementing the deployment |
| 52 | +counter in Charmworld. |
| 53 | |
| 54 | After receiving a deployment request, the DeployMiddleware sends a response |
| 55 | indicating whether or not the request has been accepted. This response is sent |
| 56 | |
| 57 | === modified file 'server/guiserver/bundles/base.py' |
| 58 | --- server/guiserver/bundles/base.py 2014-12-17 13:19:51 +0000 |
| 59 | +++ server/guiserver/bundles/base.py 2015-02-17 17:42:45 +0000 |
| 60 | @@ -97,7 +97,7 @@ |
| 61 | self.importer_options = blocking.get_default_guiserver_options() |
| 62 | |
| 63 | @gen.coroutine |
| 64 | - def validate(self, user, name, bundle): |
| 65 | + def validate(self, user, bundle): |
| 66 | """Validate the deployment bundle. |
| 67 | |
| 68 | The validation is executed in a separate process using the |
| 69 | @@ -105,7 +105,6 @@ |
| 70 | |
| 71 | Three arguments are provided: |
| 72 | - user: the current authenticated user; |
| 73 | - - name: then name of the bundle to be imported; |
| 74 | - bundle: a YAML decoded object representing the bundle contents. |
| 75 | |
| 76 | Return a Future whose result is a string representing an error or None |
| 77 | @@ -272,6 +271,7 @@ |
| 78 | self._deployer = deployer |
| 79 | self._write_response = write_response |
| 80 | self.routes = { |
| 81 | + # Default import route |
| 82 | 'Import': views.import_bundle, |
| 83 | 'Watch': views.watch, |
| 84 | 'Next': views.next, |
| 85 | @@ -291,8 +291,9 @@ |
| 86 | def process_request(self, data): |
| 87 | """Process a deployment request.""" |
| 88 | request_id = data['RequestId'] |
| 89 | + params = data.get('Params', {}) |
| 90 | view = self.routes[data['Request']] |
| 91 | - request = ObjectDict(params=data.get('Params', {}), user=self._user) |
| 92 | + request = ObjectDict(params=params, user=self._user) |
| 93 | response = yield view(request, self._deployer) |
| 94 | response['RequestId'] = request_id |
| 95 | self._write_response(response) |
| 96 | |
| 97 | === modified file 'server/guiserver/bundles/views.py' |
| 98 | --- server/guiserver/bundles/views.py 2013-11-19 14:56:29 +0000 |
| 99 | +++ server/guiserver/bundles/views.py 2015-02-17 17:42:45 +0000 |
| 100 | @@ -76,12 +76,15 @@ |
| 101 | """Parse the request data and return a (name, bundle, bundle_id) tuple. |
| 102 | |
| 103 | In the tuple: |
| 104 | - - name is the name of the bundle to be imported; |
| 105 | + - name is the name of the bundle to be imported; this is required for old |
| 106 | + style bundles, not for v4 bundles |
| 107 | - bundle is the YAML decoded bundle object. |
| 108 | - - bundle_id is the permanent id of the bundle of the form |
| 109 | + - bundle_id is the permanent id of the bundle, in v3 of the form |
| 110 | ~user/basketname/version/bundlename, e.g. |
| 111 | ~jorge/mediawiki/3/mediawiki-simple. The bundle_id is optional and |
| 112 | will be None if not given. |
| 113 | + In v4, the bundle ID is in the form ~user/bundlename, e.g. |
| 114 | + ~jorge/mediawiki-simple and is required. |
| 115 | |
| 116 | Raise a ValueError if data represents an invalid request. |
| 117 | """ |
| 118 | @@ -92,6 +95,11 @@ |
| 119 | bundles = yaml.safe_load(contents) |
| 120 | except Exception as err: |
| 121 | raise ValueError('invalid YAML contents: {}'.format(err)) |
| 122 | + if params.get('Version') == 4: |
| 123 | + bundle_id = params.get('BundleID') |
| 124 | + return bundle_id, bundles, bundle_id |
| 125 | + |
| 126 | + # This is an old-style bundle. |
| 127 | name = params.get('Name') |
| 128 | if name is None: |
| 129 | # The Name is optional if the YAML contents contain only one bundle. |
| 130 | @@ -116,7 +124,12 @@ |
| 131 | assigned to the bundle deployment. |
| 132 | |
| 133 | Request: 'Import'. |
| 134 | - Parameters example: {'Name': 'bundle-name', 'YAML': 'bundles'}. |
| 135 | + Parameters example: { |
| 136 | + 'Name': 'bundle-name', |
| 137 | + 'YAML': 'bundles', |
| 138 | + 'Version': 4, |
| 139 | + 'BundleID': '~user/bundle-name', |
| 140 | + }. |
| 141 | """ |
| 142 | # Validate the request parameters. |
| 143 | try: |
| 144 | @@ -130,7 +143,7 @@ |
| 145 | error = 'invalid request: invalid bundle {}: {}'.format(name, err) |
| 146 | raise response(error=error) |
| 147 | # Validate the bundle against the current state of the Juju environment. |
| 148 | - err = yield deployer.validate(request.user, name, bundle) |
| 149 | + err = yield deployer.validate(request.user, bundle) |
| 150 | if err is not None: |
| 151 | raise response(error='invalid request: {}'.format(err)) |
| 152 | # Add the bundle deployment to the Deployer queue. |
| 153 | |
| 154 | === modified file 'server/guiserver/tests/bundles/test_base.py' |
| 155 | --- server/guiserver/tests/bundles/test_base.py 2014-12-17 13:19:51 +0000 |
| 156 | +++ server/guiserver/tests/bundles/test_base.py 2015-02-17 17:42:45 +0000 |
| 157 | @@ -86,7 +86,7 @@ |
| 158 | # None is returned if the validation succeeds. |
| 159 | deployer = self.make_deployer() |
| 160 | with self.patch_validate(): |
| 161 | - result = yield deployer.validate(self.user, 'bundle', self.bundle) |
| 162 | + result = yield deployer.validate(self.user, self.bundle) |
| 163 | self.assertIsNone(result) |
| 164 | |
| 165 | @gen_test |
| 166 | @@ -95,7 +95,7 @@ |
| 167 | deployer = self.make_deployer() |
| 168 | error = ValueError('validation error') |
| 169 | with self.patch_validate(side_effect=error): |
| 170 | - result = yield deployer.validate(self.user, 'bundle', self.bundle) |
| 171 | + result = yield deployer.validate(self.user, self.bundle) |
| 172 | self.assertEqual(str(error), result) |
| 173 | |
| 174 | @gen_test |
| 175 | @@ -103,7 +103,7 @@ |
| 176 | # The validation is executed in a separate process. |
| 177 | deployer = self.make_deployer() |
| 178 | with self.patch_validate() as mock_validate: |
| 179 | - yield deployer.validate(self.user, 'bundle', self.bundle) |
| 180 | + yield deployer.validate(self.user, self.bundle) |
| 181 | mock_validate.assert_called_once_with( |
| 182 | self.apiurl, self.user.username, self.user.password, self.bundle) |
| 183 | mock_validate.assert_called_in_a_separate_process() |
| 184 | @@ -112,7 +112,7 @@ |
| 185 | def test_unsupported_api_version(self): |
| 186 | # An error message is returned the API version is not supported. |
| 187 | deployer = self.make_deployer(apiversion='not-supported') |
| 188 | - result = yield deployer.validate(self.user, 'bundle', self.bundle) |
| 189 | + result = yield deployer.validate(self.user, self.bundle) |
| 190 | self.assertEqual('unsupported API version: not-supported', result) |
| 191 | |
| 192 | def test_import_bundle_scheduling(self): |
| 193 | @@ -422,7 +422,7 @@ |
| 194 | self.deployment = base.DeployMiddleware( |
| 195 | self.user, self.deployer, self.responses.append) |
| 196 | |
| 197 | - def test_deployment_requested(self): |
| 198 | + def test_deployment_requested_v3(self): |
| 199 | # True is returned if the incoming data is a deployment request. |
| 200 | requests = ( |
| 201 | self.make_deployment_request('Import'), |
| 202 | @@ -434,7 +434,19 @@ |
| 203 | requested = self.deployment.requested(request) |
| 204 | self.assertTrue(requested, request) |
| 205 | |
| 206 | - def test_deployment_not_requested(self): |
| 207 | + def test_deployment_requested_v4(self): |
| 208 | + # True is returned if the incoming data is a deployment request. |
| 209 | + requests = ( |
| 210 | + self.make_deployment_request('Import', version=4), |
| 211 | + self.make_deployment_request('Watch', version=4), |
| 212 | + self.make_deployment_request('Next', version=4), |
| 213 | + self.make_deployment_request('Status', version=4), |
| 214 | + ) |
| 215 | + for request in requests: |
| 216 | + requested = self.deployment.requested(request) |
| 217 | + self.assertTrue(requested, request) |
| 218 | + |
| 219 | + def test_deployment_not_requested_v3(self): |
| 220 | # False is returned if the incoming data is not a deployment request. |
| 221 | # Params are not validated by DeployMiddleware.requested. |
| 222 | params = {'Name': 'mybundle', 'YAML': 'foo: bar'} |
| 223 | @@ -474,8 +486,48 @@ |
| 224 | requested = self.deployment.requested(request) |
| 225 | self.assertFalse(requested, request) |
| 226 | |
| 227 | + def test_deployment_not_requested_v4(self): |
| 228 | + # False is returned if the incoming data is not a deployment request. |
| 229 | + # Params are not validated by DeployMiddleware.requested. |
| 230 | + params = {'Name': 'mybundle', 'YAML': 'foo: bar', 'Version': 4} |
| 231 | + requests = ( |
| 232 | + # Empty request. |
| 233 | + {}, |
| 234 | + # Invalid type field. |
| 235 | + { |
| 236 | + 'RequestId': 1, |
| 237 | + 'Type': 'INVALID', |
| 238 | + 'Request': 'Import', |
| 239 | + 'Params': params, |
| 240 | + }, |
| 241 | + # Invalid request field. |
| 242 | + { |
| 243 | + 'RequestId': 2, |
| 244 | + 'Type': 'Deployer', |
| 245 | + 'Request': 'INVALID', |
| 246 | + 'Params': params, |
| 247 | + }, |
| 248 | + # Missing request id field. |
| 249 | + { |
| 250 | + 'INVALID': 3, |
| 251 | + 'Type': 'Deployer', |
| 252 | + 'Request': 'Import', |
| 253 | + 'Params': params, |
| 254 | + }, |
| 255 | + # Field names are case sensitive. |
| 256 | + { |
| 257 | + 'RequestId': 4, |
| 258 | + 'type': 'Deployer', |
| 259 | + 'request': 'Import', |
| 260 | + 'Params': params, |
| 261 | + }, |
| 262 | + ) |
| 263 | + for request in requests: |
| 264 | + requested = self.deployment.requested(request) |
| 265 | + self.assertFalse(requested, request) |
| 266 | + |
| 267 | @gen_test |
| 268 | - def test_process_request(self): |
| 269 | + def test_process_request_v3(self): |
| 270 | # A deployment request is correctly processed. |
| 271 | deployment_request = self.make_deployment_request('Import') |
| 272 | |
| 273 | @@ -495,3 +547,25 @@ |
| 274 | self.assertEqual(1, len(self.responses)) |
| 275 | response = self.responses[0] |
| 276 | self.assertEqual({'RequestId': 42, 'Response': 'ok'}, response) |
| 277 | + |
| 278 | + @gen_test |
| 279 | + def test_process_request_v4(self): |
| 280 | + # A deployment request is correctly processed. |
| 281 | + deployment_request = self.make_deployment_request('Import', version=4) |
| 282 | + |
| 283 | + @gen.coroutine |
| 284 | + def view(request, deployer): |
| 285 | + # Ensure the view is called with the expected arguments. |
| 286 | + self.assertEqual(deployment_request['Params'], request.params) |
| 287 | + self.assertIs(self.user, request.user) |
| 288 | + self.assertIs(self.deployer, deployer) |
| 289 | + return {'Response': 'ok'} |
| 290 | + |
| 291 | + # Patch the routes so that the customized view defined above is called |
| 292 | + # when an import request is processed. |
| 293 | + self.deployment.routes['Import'] = view |
| 294 | + yield self.deployment.process_request(deployment_request) |
| 295 | + # Ensure the response has been correctly sent. |
| 296 | + self.assertEqual(1, len(self.responses)) |
| 297 | + response = self.responses[0] |
| 298 | + self.assertEqual({'RequestId': 42, 'Response': 'ok'}, response) |
| 299 | |
| 300 | === modified file 'server/guiserver/tests/bundles/test_views.py' |
| 301 | --- server/guiserver/tests/bundles/test_views.py 2013-11-19 15:57:08 +0000 |
| 302 | +++ server/guiserver/tests/bundles/test_views.py 2015-02-17 17:42:45 +0000 |
| 303 | @@ -84,7 +84,7 @@ |
| 304 | self.assertEqual(0, len(self.deployer.mock_calls)) |
| 305 | |
| 306 | |
| 307 | -class TestImportBundle( |
| 308 | +class TestImportBundleV3( |
| 309 | ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase, |
| 310 | AsyncTestCase): |
| 311 | |
| 312 | @@ -189,7 +189,7 @@ |
| 313 | self.assertEqual(expected_response, response) |
| 314 | # The Deployer validate method has been called. |
| 315 | self.deployer.validate.assert_called_once_with( |
| 316 | - request.user, 'mybundle', {'services': {}}) |
| 317 | + request.user, {'services': {}}) |
| 318 | |
| 319 | @gen_test |
| 320 | def test_success(self): |
| 321 | @@ -204,7 +204,7 @@ |
| 322 | expected_response = {'Response': {'DeploymentId': 42}} |
| 323 | self.assertEqual(expected_response, response) |
| 324 | # Ensure the Deployer methods have been correctly called. |
| 325 | - args = (request.user, 'mybundle', {'services': {}}) |
| 326 | + args = (request.user, {'services': {}}) |
| 327 | self.deployer.validate.assert_called_once_with(*args) |
| 328 | args = (request.user, 'mybundle', {'services': {}}, None) |
| 329 | self.deployer.import_bundle.assert_called_once_with(*args) |
| 330 | @@ -258,13 +258,149 @@ |
| 331 | # Execute the view. |
| 332 | yield self.view(request, self.deployer) |
| 333 | # Ensure the Deployer methods have been correctly called. |
| 334 | - args = (request.user, 'mybundle', {'services': {}}) |
| 335 | + args = (request.user, {'services': {}}) |
| 336 | self.deployer.validate.assert_called_once_with(*args) |
| 337 | args = (request.user, 'mybundle', {'services': {}}, |
| 338 | '~jorge/wiki/3/smallwiki') |
| 339 | self.deployer.import_bundle.assert_called_once_with(*args) |
| 340 | |
| 341 | |
| 342 | +class TestImportBundleV4( |
| 343 | + ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase, |
| 344 | + AsyncTestCase): |
| 345 | + |
| 346 | + def get_view(self): |
| 347 | + return views.import_bundle |
| 348 | + |
| 349 | + @gen_test |
| 350 | + def test_invalid_yaml(self): |
| 351 | + # An error response is returned if an invalid YAML encoded string is |
| 352 | + # passed. |
| 353 | + params = {'Name': 'bundle-name', 'Version': 4, 'YAML': 42} |
| 354 | + request = self.make_view_request(params=params) |
| 355 | + response = yield self.view(request, self.deployer) |
| 356 | + expected_response = { |
| 357 | + 'Response': {}, |
| 358 | + 'Error': 'invalid request: invalid YAML contents: ' |
| 359 | + "'int' object has no attribute 'read'", |
| 360 | + } |
| 361 | + self.assertEqual(expected_response, response) |
| 362 | + # The Deployer methods have not been called. |
| 363 | + self.assertEqual(0, len(self.deployer.mock_calls)) |
| 364 | + |
| 365 | + @gen_test |
| 366 | + def test_invalid_bundle(self): |
| 367 | + # An error response is returned if the bundle is not well formed. |
| 368 | + params = {'YAML': 'not valid', 'Version': 4, 'BundleID': 'foo'} |
| 369 | + request = self.make_view_request(params=params) |
| 370 | + response = yield self.view(request, self.deployer) |
| 371 | + expected_response = { |
| 372 | + 'Response': {}, |
| 373 | + 'Error': 'invalid request: invalid bundle foo: ' |
| 374 | + 'the bundle data is not well formed', |
| 375 | + } |
| 376 | + self.assertEqual(expected_response, response) |
| 377 | + # The Deployer methods have not been called. |
| 378 | + self.assertEqual(0, len(self.deployer.mock_calls)) |
| 379 | + |
| 380 | + @gen_test |
| 381 | + def test_invalid_bundle_constraints(self): |
| 382 | + # An error response is returned if the bundle includes services with |
| 383 | + # unsupported constraints. |
| 384 | + params = { |
| 385 | + 'YAML': 'services: {django: {constraints: invalid=1}}', |
| 386 | + 'Version': 4, |
| 387 | + 'BundleID': 'foo' |
| 388 | + } |
| 389 | + request = self.make_view_request(params=params) |
| 390 | + response = yield self.view(request, self.deployer) |
| 391 | + expected_response = { |
| 392 | + 'Response': {}, |
| 393 | + 'Error': 'invalid request: invalid bundle foo: ' |
| 394 | + 'unsupported constraints: invalid', |
| 395 | + } |
| 396 | + self.assertEqual(expected_response, response) |
| 397 | + # The Deployer methods have not been called. |
| 398 | + self.assertEqual(0, len(self.deployer.mock_calls)) |
| 399 | + |
| 400 | + @gen_test |
| 401 | + def test_undeployable_bundle(self): |
| 402 | + # An error response is returned if the bundle cannot be imported in the |
| 403 | + # current Juju environment. |
| 404 | + params = {'Version': 4, 'YAML': 'services: {}'} |
| 405 | + request = self.make_view_request(params=params) |
| 406 | + # Simulate an error returned by the Deployer validate method. |
| 407 | + self.deployer.validate.return_value = self.make_future('an error') |
| 408 | + # Execute the view. |
| 409 | + response = yield self.view(request, self.deployer) |
| 410 | + expected_response = { |
| 411 | + 'Response': {}, |
| 412 | + 'Error': 'invalid request: an error', |
| 413 | + } |
| 414 | + self.assertEqual(expected_response, response) |
| 415 | + # The Deployer validate method has been called. |
| 416 | + self.deployer.validate.assert_called_once_with( |
| 417 | + request.user, {'services': {}}) |
| 418 | + |
| 419 | + @gen_test |
| 420 | + def test_success(self): |
| 421 | + # The response includes the deployment identifier. |
| 422 | + params = {'BundleID': 'foo', 'Version': 4, 'YAML': 'services: {}'} |
| 423 | + request = self.make_view_request(params=params) |
| 424 | + # Set up the Deployer mock. |
| 425 | + self.deployer.validate.return_value = self.make_future(None) |
| 426 | + self.deployer.import_bundle.return_value = 42 |
| 427 | + # Execute the view. |
| 428 | + response = yield self.view(request, self.deployer) |
| 429 | + expected_response = {'Response': {'DeploymentId': 42}} |
| 430 | + self.assertEqual(expected_response, response) |
| 431 | + # Ensure the Deployer methods have been correctly called. |
| 432 | + args = (request.user, {'services': {}}) |
| 433 | + self.deployer.validate.assert_called_once_with(*args) |
| 434 | + args = (request.user, 'foo', {'services': {}}, 'foo') |
| 435 | + self.deployer.import_bundle.assert_called_once_with(*args) |
| 436 | + |
| 437 | + @gen_test |
| 438 | + def test_logging(self): |
| 439 | + # The beginning of the bundle import process is properly logged. |
| 440 | + params = {'BundleID': 'foo', 'Version': 4, 'YAML': 'services: {}'} |
| 441 | + request = self.make_view_request(params=params) |
| 442 | + # Set up the Deployer mock. |
| 443 | + self.deployer.validate.return_value = self.make_future(None) |
| 444 | + self.deployer.import_bundle.return_value = 42 |
| 445 | + # Execute the view. |
| 446 | + expected_log = "import_bundle: scheduling 'foo' deployment" |
| 447 | + with ExpectLog('', expected_log, required=True): |
| 448 | + yield self.view(request, self.deployer) |
| 449 | + |
| 450 | + # The following tests exercise views._validate_import_params directly. |
| 451 | + def test_id_provided(self): |
| 452 | + params = {'YAML': 'services: {}', |
| 453 | + 'Version': 4, |
| 454 | + 'BundleID': '~jorge/wiki'} |
| 455 | + results = views._validate_import_params(params) |
| 456 | + expected = ('~jorge/wiki', {'services': {}}, '~jorge/wiki') |
| 457 | + self.assertEqual(expected, results) |
| 458 | + |
| 459 | + @gen_test |
| 460 | + def test_id_passed_to_deployer(self): |
| 461 | + params = {'YAML': 'services: {}', |
| 462 | + 'Version': 4, |
| 463 | + 'BundleID': '~jorge/wiki/3/smallwiki'} |
| 464 | + request = self.make_view_request(params=params) |
| 465 | + # Set up the Deployer mock. |
| 466 | + self.deployer.validate.return_value = self.make_future(None) |
| 467 | + self.deployer.import_bundle.return_value = 42 |
| 468 | + # Execute the view. |
| 469 | + yield self.view(request, self.deployer) |
| 470 | + # Ensure the Deployer methods have been correctly called. |
| 471 | + args = (request.user, {'services': {}}) |
| 472 | + self.deployer.validate.assert_called_once_with(*args) |
| 473 | + args = (request.user, '~jorge/wiki/3/smallwiki', {'services': {}}, |
| 474 | + '~jorge/wiki/3/smallwiki') |
| 475 | + self.deployer.import_bundle.assert_called_once_with(*args) |
| 476 | + |
| 477 | + |
| 478 | class TestWatch( |
| 479 | ViewsTestMixin, helpers.BundlesTestMixin, LogTrapTestCase, |
| 480 | AsyncTestCase): |
| 481 | |
| 482 | === modified file 'server/guiserver/tests/helpers.py' |
| 483 | --- server/guiserver/tests/helpers.py 2014-01-28 15:48:59 +0000 |
| 484 | +++ server/guiserver/tests/helpers.py 2015-02-17 17:42:45 +0000 |
| 485 | @@ -143,19 +143,26 @@ |
| 486 | return mock.Mock(params=params, user=user) |
| 487 | |
| 488 | def make_deployment_request( |
| 489 | - self, request, request_id=42, params=None, encoded=False): |
| 490 | + self, request, request_id=42, params=None, encoded=False, |
| 491 | + version=None): |
| 492 | """Create and return a deployment request message. |
| 493 | |
| 494 | If encoded is set to True, the returned message will be JSON encoded. |
| 495 | """ |
| 496 | + if version is None: |
| 497 | + bundle = 'bundle: {services: {}}' |
| 498 | + else: |
| 499 | + bundle = 'services: {}' |
| 500 | defaults = { |
| 501 | - 'Import': {'Name': 'bundle', 'YAML': 'bundle: {services: {}}'}, |
| 502 | + 'Import': {'Name': 'bundle', 'YAML': bundle}, |
| 503 | 'Watch': {'DeploymentId': 0}, |
| 504 | 'Next': {'WatcherId': 0}, |
| 505 | 'Status': {}, |
| 506 | } |
| 507 | if params is None: |
| 508 | params = defaults[request] |
| 509 | + if version is not None: |
| 510 | + params['Version'] = version |
| 511 | data = { |
| 512 | 'RequestId': request_id, |
| 513 | 'Type': 'Deployer', |

Hi Madison,
could you please re-propose this against the development branch?
It's lp:~juju-gui/charms/trusty/juju-gui/trunk
Thank you.