OLD | NEW |
1 /* | 1 /* |
2 This file is part of the Juju GUI, which lets users view and manage Juju | 2 This file is part of the Juju GUI, which lets users view and manage Juju |
3 environments within a graphical interface (https://launchpad.net/juju-gui). | 3 environments within a graphical interface (https://launchpad.net/juju-gui). |
4 Copyright (C) 2012-2013 Canonical Ltd. | 4 Copyright (C) 2012-2013 Canonical Ltd. |
5 | 5 |
6 This program is free software: you can redistribute it and/or modify it under | 6 This program is free software: you can redistribute it and/or modify it under |
7 the terms of the GNU Affero General Public License version 3, as published by | 7 the terms of the GNU Affero General Public License version 3, as published by |
8 the Free Software Foundation. | 8 the Free Software Foundation. |
9 | 9 |
10 This program is distributed in the hope that it will be useful, but WITHOUT | 10 This program is distributed in the hope that it will be useful, but WITHOUT |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
171 | 171 |
172 it('refuses to receive asynchronously when not connected.', function() { | 172 it('refuses to receive asynchronously when not connected.', function() { |
173 var conn = new ClientConnection({juju: {open: function() {}}}); | 173 var conn = new ClientConnection({juju: {open: function() {}}}); |
174 assert.throws( | 174 assert.throws( |
175 conn.receive.bind(conn, {response: 42}), | 175 conn.receive.bind(conn, {response: 42}), |
176 'INVALID_STATE_ERR : Connection is closed.'); | 176 'INVALID_STATE_ERR : Connection is closed.'); |
177 }); | 177 }); |
178 | 178 |
179 }); | 179 }); |
180 | 180 |
181 describe('sandbox.PyJujuAPI', function() { | |
182 var requires = [ | |
183 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-python', | |
184 'juju-models', 'promise']; | |
185 var Y, sandboxModule, ClientConnection, environmentsModule, state, juju, | |
186 client, env, utils, cleanups; | |
187 | |
188 before(function(done) { | |
189 Y = YUI(GlobalConfig).use(requires, function(Y) { | |
190 sandboxModule = Y.namespace('juju.environments.sandbox'); | |
191 environmentsModule = Y.namespace('juju.environments'); | |
192 utils = Y.namespace('juju-tests.utils'); | |
193 // A global variable required for testing. | |
194 window.flags = {}; | |
195 done(); | |
196 }); | |
197 }); | |
198 | |
199 beforeEach(function() { | |
200 state = utils.makeFakeBackendWithCharmStore(); | |
201 juju = new sandboxModule.PyJujuAPI({state: state}); | |
202 client = new sandboxModule.ClientConnection({juju: juju}); | |
203 env = new environmentsModule.PythonEnvironment({conn: client}); | |
204 cleanups = []; | |
205 }); | |
206 | |
207 afterEach(function() { | |
208 Y.each(cleanups, function(f) {f();}); | |
209 env.destroy(); | |
210 client.destroy(); | |
211 juju.destroy(); | |
212 state.destroy(); | |
213 }); | |
214 | |
215 after(function() { | |
216 delete window.flags; | |
217 }); | |
218 | |
219 /** | |
220 Generates the services required for some tests. After the services have | |
221 been generated it will call the supplied callback. | |
222 | |
223 This interacts directly with the fakebackend bypassing the environment. | |
224 The test "can add additional units" tests this code directly so as long | |
225 as it passes you can consider this method valid. | |
226 | |
227 @method generateServices | |
228 @param {Function} callback The callback to call after the services have | |
229 been generated. | |
230 */ | |
231 function generateServices(callback) { | |
232 state.deploy('cs:wordpress', function(service) { | |
233 var data = { | |
234 op: 'add_unit', | |
235 service_name: 'wordpress', | |
236 num_units: 2 | |
237 }; | |
238 state.nextChanges(); | |
239 client.onmessage = function() { | |
240 client.onmessage = function(received) { | |
241 // After done generating the services | |
242 callback(received); | |
243 }; | |
244 client.send(Y.JSON.stringify(data)); | |
245 }; | |
246 client.open(); | |
247 }); | |
248 } | |
249 | |
250 /** | |
251 Generates the two services required for relation removal tests. After the | |
252 services have been generated, a relation between them will be added and | |
253 then removed. | |
254 | |
255 This interacts directly with the fakebackend bypassing the environment. | |
256 | |
257 @method generateAndRelateServices | |
258 @param {Array} charms The URLs of two charms to be deployed. | |
259 @param {Array} relation Two endpoint strings to be related. | |
260 @param {Array} removeRelation Two enpoint strings identifying | |
261 a relation to be removed. | |
262 @param {Object} mock Object with the expected return values of | |
263 the relation removal operation. | |
264 @param {Function} done To be called to signal the test end. | |
265 @return {undefined} Side effects only. | |
266 */ | |
267 function generateAndRelateServices(charms, relation, | |
268 removeRelation, mock, done) { | |
269 state.deploy(charms[0], function() { | |
270 state.deploy(charms[1], function() { | |
271 if (relation) { | |
272 state.addRelation(relation[0], relation[1]); | |
273 } | |
274 var data = { | |
275 op: 'remove_relation', | |
276 endpoint_a: removeRelation[0], | |
277 endpoint_b: removeRelation[1] | |
278 }; | |
279 client.onmessage = function(received) { | |
280 var recData = Y.JSON.parse(received.data); | |
281 // Skip the defaultSeriesChange message. | |
282 if (recData.default_series === undefined) { | |
283 assert.equal(recData.result, mock.result); | |
284 assert.equal(recData.err, mock.err); | |
285 if (!recData.err) { | |
286 assert.equal(recData.endpoint_a, mock.endpoint_a); | |
287 assert.equal(recData.endpoint_b, mock.endpoint_b); | |
288 } | |
289 done(); | |
290 } | |
291 }; | |
292 client.open(); | |
293 client.send(Y.JSON.stringify(data)); | |
294 }); | |
295 }); | |
296 } | |
297 | |
298 /** | |
299 Same as generateServices but uses the environment integration methods. | |
300 Should be considered valid if "can add additional units (integration)" | |
301 test passes. | |
302 | |
303 @method generateIntegrationServices | |
304 @param {Function} callback The callback to call after the services have | |
305 been generated. | |
306 */ | |
307 function generateIntegrationServices(callback) { | |
308 env.after('defaultSeriesChange', function() { | |
309 var localCb = function(result) { | |
310 env.add_unit('kumquat', 2, function(data) { | |
311 // After finished generating integrated services | |
312 callback(data); | |
313 }); | |
314 }; | |
315 env.deploy( | |
316 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb); | |
317 }); | |
318 env.connect(); | |
319 } | |
320 | |
321 /** | |
322 Generates the services and then exposes them for the un/expose tests. | |
323 After they have been exposed it calls the supplied callback. | |
324 | |
325 This interacts directly with the fakebackend bypassing the environment and | |
326 should be considered valid if "can expose a service" test passes. | |
327 | |
328 @method generateAndExposeService | |
329 @param {Function} callback The callback to call after the services have | |
330 been generated. | |
331 */ | |
332 function generateAndExposeService(callback) { | |
333 state.deploy('cs:wordpress', function(data) { | |
334 var command = { | |
335 op: 'expose', | |
336 service_name: data.service.get('name') | |
337 }; | |
338 state.nextChanges(); | |
339 client.onmessage = function() { | |
340 client.onmessage = function(rec) { | |
341 callback(rec); | |
342 }; | |
343 client.send(Y.JSON.stringify(command)); | |
344 }; | |
345 client.open(); | |
346 }, { unitCount: 1 }); | |
347 } | |
348 | |
349 /** | |
350 Same as generateAndExposeService but uses the environment integration | |
351 methods. Should be considered valid if "can expose a service | |
352 (integration)" test passes. | |
353 | |
354 @method generateAndExposeIntegrationService | |
355 @param {Function} callback The callback to call after the services have | |
356 been generated. | |
357 */ | |
358 function generateAndExposeIntegrationService(callback) { | |
359 env.after('defaultSeriesChange', function() { | |
360 var localCb = function(result) { | |
361 env.expose(result.service_name, function(rec) { | |
362 callback(rec); | |
363 }); | |
364 }; | |
365 env.deploy( | |
366 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb); | |
367 }); | |
368 env.connect(); | |
369 } | |
370 | |
371 it('opens successfully.', function(done) { | |
372 var isAsync = false; | |
373 client.onmessage = function(message) { | |
374 assert.isTrue(isAsync); | |
375 assert.deepEqual( | |
376 Y.JSON.parse(message.data), | |
377 { | |
378 ready: true, | |
379 provider_type: 'demonstration', | |
380 default_series: 'precise' | |
381 }); | |
382 done(); | |
383 }; | |
384 assert.isFalse(juju.connected); | |
385 assert.isUndefined(juju.get('client')); | |
386 client.open(); | |
387 assert.isTrue(juju.connected); | |
388 assert.strictEqual(juju.get('client'), client); | |
389 isAsync = true; | |
390 }); | |
391 | |
392 it('ignores "open" when already open to same client.', function() { | |
393 client.receive = function() { | |
394 assert.ok(false, 'The receive method should not be called.'); | |
395 }; | |
396 // Whitebox test: duplicate "open" state. | |
397 juju.connected = true; | |
398 juju.set('client', client); | |
399 // This is effectively a re-open. | |
400 client.open(); | |
401 // The assert.ok above is the verification. | |
402 }); | |
403 | |
404 it('refuses to open if already open to another client.', function() { | |
405 // This is a simple way to make sure that we don't leave multiple | |
406 // setInterval calls running. If for some reason we want more | |
407 // simultaneous clients, that's fine, though that will require | |
408 // reworking the delta code generally. | |
409 juju.connected = true; | |
410 juju.set('client', {receive: function() { | |
411 assert.ok(false, 'The receive method should not have been called.'); | |
412 }}); | |
413 assert.throws( | |
414 client.open.bind(client), | |
415 'INVALID_STATE_ERR : Connection is open to another client.'); | |
416 }); | |
417 | |
418 it('closes successfully.', function(done) { | |
419 client.onmessage = function() { | |
420 client.close(); | |
421 assert.isFalse(juju.connected); | |
422 assert.isUndefined(juju.get('client')); | |
423 done(); | |
424 }; | |
425 client.open(); | |
426 }); | |
427 | |
428 it('ignores "close" when already closed.', function() { | |
429 // This simply shows that we do not raise an error. | |
430 juju.close(); | |
431 }); | |
432 | |
433 it('can dispatch on received information.', function(done) { | |
434 var data = {op: 'testingTesting123', foo: 'bar'}; | |
435 juju.performOp_testingTesting123 = function(received) { | |
436 assert.notStrictEqual(received, data); | |
437 assert.deepEqual(received, data); | |
438 done(); | |
439 }; | |
440 client.open(); | |
441 client.send(Y.JSON.stringify(data)); | |
442 }); | |
443 | |
444 it('refuses to dispatch when closed.', function() { | |
445 assert.throws( | |
446 juju.receive.bind(juju, {}), | |
447 'INVALID_STATE_ERR : Connection is closed.' | |
448 ); | |
449 }); | |
450 | |
451 it('can log in.', function(done) { | |
452 state.logout(); | |
453 // See FakeBackend's authorizedUsers for these default authentication | |
454 // values. | |
455 var data = { | |
456 op: 'login', | |
457 user: 'admin', | |
458 password: 'password', | |
459 request_id: 42 | |
460 }; | |
461 client.onmessage = function(received) { | |
462 // First message is the provider type and default series. We ignore | |
463 // it, and prepare for the next one, which will be the reply to our | |
464 // login. | |
465 client.onmessage = function(received) { | |
466 data.result = true; | |
467 assert.deepEqual(Y.JSON.parse(received.data), data); | |
468 assert.isTrue(state.get('authenticated')); | |
469 done(); | |
470 }; | |
471 client.send(Y.JSON.stringify(data)); | |
472 }; | |
473 client.open(); | |
474 }); | |
475 | |
476 it('can log in (environment integration).', function(done) { | |
477 state.logout(); | |
478 env.after('defaultSeriesChange', function() { | |
479 // See FakeBackend's authorizedUsers for these default values. | |
480 env.setCredentials({user: 'admin', password: 'password'}); | |
481 env.after('login', function() { | |
482 assert.isTrue(env.userIsAuthenticated); | |
483 done(); | |
484 }); | |
485 env.login(); | |
486 }); | |
487 env.connect(); | |
488 }); | |
489 | |
490 it('can deploy.', function(done) { | |
491 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
492 var data = { | |
493 op: 'deploy', | |
494 charm_url: 'cs:wordpress', | |
495 service_name: 'kumquat', | |
496 config_raw: 'funny: business', | |
497 num_units: 2, | |
498 request_id: 42 | |
499 }; | |
500 client.onmessage = function(received) { | |
501 // First message is the provider type and default series. We ignore | |
502 // it, and prepare for the next one, which will be the reply to our | |
503 // deployment. | |
504 client.onmessage = function(received) { | |
505 var parsed = Y.JSON.parse(received.data); | |
506 assert.isUndefined(parsed.err); | |
507 assert.deepEqual(parsed, data); | |
508 assert.isObject( | |
509 state.db.charms.getById('cs:precise/wordpress-10')); | |
510 var service = state.db.services.getById('kumquat'); | |
511 assert.isObject(service); | |
512 assert.equal(service.get('charm'), 'cs:precise/wordpress-10'); | |
513 assert.deepEqual(service.get('config'), {funny: 'business'}); | |
514 var units = state.db.units.get_units_for_service(service); | |
515 assert.lengthOf(units, 2); | |
516 done(); | |
517 }; | |
518 client.send(Y.JSON.stringify(data)); | |
519 }; | |
520 client.open(); | |
521 }); | |
522 | |
523 it('can deploy (environment integration).', function(done) { | |
524 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
525 env.after('defaultSeriesChange', function() { | |
526 var callback = function(result) { | |
527 assert.isUndefined(result.err); | |
528 assert.equal(result.charm_url, 'cs:wordpress'); | |
529 var service = state.db.services.getById('kumquat'); | |
530 assert.equal(service.get('charm'), 'cs:precise/wordpress-10'); | |
531 assert.deepEqual(service.get('config'), {llama: 'pajama'}); | |
532 done(); | |
533 }; | |
534 env.deploy( | |
535 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback); | |
536 }); | |
537 env.connect(); | |
538 }); | |
539 | |
540 it('can communicate errors after attempting to deploy', function(done) { | |
541 // Create a service with the name "wordpress". | |
542 // The charm store is synchronous in tests, so we don't need a real | |
543 // callback. | |
544 state.deploy('cs:wordpress', function() {}); | |
545 env.after('defaultSeriesChange', function() { | |
546 var callback = function(result) { | |
547 assert.equal( | |
548 result.err, 'A service with this name already exists.'); | |
549 done(); | |
550 }; | |
551 env.deploy( | |
552 'cs:wordpress', undefined, undefined, undefined, 1, callback); | |
553 }); | |
554 env.connect(); | |
555 }); | |
556 | |
557 it('can send a delta stream of changes.', function(done) { | |
558 // Create a service with the name "wordpress". | |
559 // The charm store is synchronous in tests, so we don't need a real | |
560 // callback. | |
561 state.deploy('cs:wordpress', function() {}); | |
562 client.onmessage = function(received) { | |
563 // First message is the provider type and default series. We ignore | |
564 // it, and prepare for the next one, which will handle the delta | |
565 // stream. | |
566 client.onmessage = function(received) { | |
567 var parsed = Y.JSON.parse(received.data); | |
568 assert.equal(parsed.op, 'delta'); | |
569 var deltas = parsed.result; | |
570 assert.lengthOf(deltas, 3); | |
571 assert.equal(deltas[0][0], 'service'); | |
572 assert.equal(deltas[0][1], 'change'); | |
573 assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10'); | |
574 assert.equal(deltas[1][0], 'machine'); | |
575 assert.equal(deltas[1][1], 'change'); | |
576 assert.equal(deltas[2][0], 'unit'); | |
577 assert.equal(deltas[2][1], 'change'); | |
578 done(); | |
579 }; | |
580 juju.sendDelta(); | |
581 }; | |
582 client.open(); | |
583 }); | |
584 | |
585 it('does not send a delta if there are no changes.', function(done) { | |
586 client.onmessage = function(received) { | |
587 // First message is the provider type and default series. We ignore | |
588 // it, and prepare for the next one, which will handle the delta | |
589 // stream. | |
590 client.receiveNow = function(response) { | |
591 assert.ok(false, 'This method should not have been called.'); | |
592 }; | |
593 juju.sendDelta(); | |
594 done(); | |
595 }; | |
596 client.open(); | |
597 }); | |
598 | |
599 it('can send a delta stream (integration).', function(done) { | |
600 // Create a service with the name "wordpress". | |
601 // The charm store is synchronous in tests, so we don't need a real | |
602 // callback. | |
603 state.deploy('cs:wordpress', function() {}, {unitCount: 2}); | |
604 var db = new Y.juju.models.Database(); | |
605 db.on('update', function() { | |
606 // We want to verify that the GUI database is equivalent to the state | |
607 // database. | |
608 assert.equal(db.services.size(), 1); | |
609 assert.equal(db.units.size(), 2); | |
610 assert.equal(db.machines.size(), 2); | |
611 var stateService = state.db.services.item(0); | |
612 var guiService = db.services.item(0); | |
613 Y.each( | |
614 ['charm', 'config', 'constraints', 'exposed', | |
615 'id', 'name', 'subordinate'], | |
616 function(attrName) { | |
617 assert.deepEqual( | |
618 guiService.get(attrName), stateService.get(attrName)); | |
619 } | |
620 ); | |
621 state.db.units.each(function(stateUnit) { | |
622 var guiUnit = db.units.getById(stateUnit.id); | |
623 Y.each( | |
624 ['agent_state', 'machine', 'number', 'service'], | |
625 function(attrName) { | |
626 assert.deepEqual(guiUnit[attrName], stateUnit[attrName]); | |
627 } | |
628 ); | |
629 }); | |
630 state.db.machines.each(function(stateMachine) { | |
631 var guiMachine = db.machines.getById(stateMachine.id); | |
632 Y.each( | |
633 ['agent_state', 'public_address', 'machine_id'], | |
634 function(attrName) { | |
635 assert.deepEqual(guiMachine[attrName], stateMachine[attrName]); | |
636 } | |
637 ); | |
638 }); | |
639 done(); | |
640 }); | |
641 env.on('delta', db.onDelta, db); | |
642 env.after('defaultSeriesChange', function() {juju.sendDelta();}); | |
643 env.connect(); | |
644 }); | |
645 | |
646 it('sends delta streams periodically after opening.', function(done) { | |
647 client.onmessage = function(received) { | |
648 // First message is the provider type and default series. We ignore | |
649 // it, and prepare for the next one, which will handle the delta | |
650 // stream. | |
651 var isAsync = false; | |
652 client.onmessage = function(received) { | |
653 assert.isTrue(isAsync); | |
654 var parsed = Y.JSON.parse(received.data); | |
655 assert.equal(parsed.op, 'delta'); | |
656 var deltas = parsed.result; | |
657 assert.lengthOf(deltas, 3); | |
658 assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10'); | |
659 done(); | |
660 }; | |
661 // Create a service with the name "wordpress". | |
662 // The charm store is synchronous in tests, so we don't need a real | |
663 // callback. | |
664 state.deploy('cs:wordpress', function() {}); | |
665 isAsync = true; | |
666 }; | |
667 juju.set('deltaInterval', 4); | |
668 client.open(); | |
669 }); | |
670 | |
671 it('stops sending delta streams after closing.', function(done) { | |
672 var sysSetInterval = window.setInterval; | |
673 var sysClearInterval = window.clearInterval; | |
674 cleanups.push(function() { | |
675 window.setInterval = sysSetInterval; | |
676 window.clearInterval = sysClearInterval; | |
677 }); | |
678 window.setInterval = function(f, interval) { | |
679 assert.isFunction(f); | |
680 assert.equal(interval, 4); | |
681 return 42; | |
682 }; | |
683 window.clearInterval = function(token) { | |
684 assert.equal(token, 42); | |
685 done(); | |
686 }; | |
687 client.onmessage = function(received) { | |
688 // First message is the provider type and default series. We can | |
689 // close now. | |
690 client.close(); | |
691 }; | |
692 juju.set('deltaInterval', 4); | |
693 client.open(); | |
694 }); | |
695 | |
696 it('can add additional units', function(done) { | |
697 function testForAddedUnits(received) { | |
698 var service = state.db.services.getById('wordpress'), | |
699 units = state.db.units.get_units_for_service(service), | |
700 data = Y.JSON.parse(received.data), | |
701 mock = { | |
702 num_units: 2, | |
703 service_name: 'wordpress', | |
704 op: 'add_unit', | |
705 result: ['wordpress/1', 'wordpress/2'] | |
706 }; | |
707 // Do we have enough total units? | |
708 assert.lengthOf(units, 3); | |
709 // Does the response object contain the proper data | |
710 assert.deepEqual(data, mock); | |
711 // Error is undefined | |
712 assert.isUndefined(data.err); | |
713 done(); | |
714 } | |
715 // Generate the default services and add units | |
716 generateServices(testForAddedUnits); | |
717 }); | |
718 | |
719 it('throws an error when adding units to an invalid service', | |
720 function(done) { | |
721 state.deploy('cs:wordpress', function(service) { | |
722 var data = { | |
723 op: 'add_unit', | |
724 service_name: 'noservice', | |
725 num_units: 2 | |
726 }; | |
727 //Clear out the delta stream | |
728 state.nextChanges(); | |
729 client.onmessage = function() { | |
730 client.onmessage = function(received) { | |
731 var data = Y.JSON.parse(received.data); | |
732 | |
733 // If there is no error data.err will be undefined | |
734 assert.equal(true, !!data.err); | |
735 done(); | |
736 }; | |
737 client.send(Y.JSON.stringify(data)); | |
738 }; | |
739 client.open(); | |
740 }); | |
741 } | |
742 ); | |
743 | |
744 it('can add additional units (integration)', function(done) { | |
745 function testForAddedUnits(data) { | |
746 var service = state.db.services.getById('kumquat'), | |
747 units = state.db.units.get_units_for_service(service); | |
748 assert.lengthOf(units, 3); | |
749 done(); | |
750 } | |
751 generateIntegrationServices(testForAddedUnits); | |
752 }); | |
753 | |
754 it('can remove units', function(done) { | |
755 function removeUnits() { | |
756 var data = { | |
757 op: 'remove_units', | |
758 unit_names: ['wordpress/0', 'wordpress/1'] | |
759 }; | |
760 client.onmessage = function(rec) { | |
761 var data = Y.JSON.parse(rec.data), | |
762 mock = { | |
763 op: 'remove_units', | |
764 result: true, | |
765 unit_names: ['wordpress/0', 'wordpress/1'] | |
766 }; | |
767 // No errors | |
768 assert.equal(data.result, true); | |
769 // Returned data object contains all information | |
770 assert.deepEqual(data, mock); | |
771 done(); | |
772 }; | |
773 client.send(Y.JSON.stringify(data)); | |
774 } | |
775 // Generate the services base data and then execute the test | |
776 generateServices(removeUnits); | |
777 }); | |
778 | |
779 it('can remove units (integration)', function(done) { | |
780 function removeUnits() { | |
781 var unitNames = ['kumquat/1', 'kumquat/2']; | |
782 env.remove_units(unitNames, function(data) { | |
783 assert.equal(data.result, true); | |
784 assert.deepEqual(data.unit_names, unitNames); | |
785 done(); | |
786 }); | |
787 } | |
788 // Generate the services via the integration method then execute the test | |
789 generateIntegrationServices(removeUnits); | |
790 }); | |
791 | |
792 it('allows attempting to remove units from an invalid service', | |
793 function(done) { | |
794 function removeUnit() { | |
795 var data = { | |
796 op: 'remove_units', | |
797 unit_names: ['bar/2'] | |
798 }; | |
799 client.onmessage = function(rec) { | |
800 var data = Y.JSON.parse(rec.data); | |
801 assert.equal(data.result, true); | |
802 done(); | |
803 }; | |
804 client.send(Y.JSON.stringify(data)); | |
805 } | |
806 // Generate the services base data then execute the test. | |
807 generateServices(removeUnit); | |
808 } | |
809 ); | |
810 | |
811 it('throws an error if unit is a subordinate', function(done) { | |
812 function removeUnits() { | |
813 var data = { | |
814 op: 'remove_units', | |
815 unit_names: ['wordpress/1'] | |
816 }; | |
817 client.onmessage = function(rec) { | |
818 var data = Y.JSON.parse(rec.data); | |
819 assert.equal(Y.Lang.isArray(data.err), true); | |
820 assert.equal(data.err.length, 1); | |
821 done(); | |
822 }; | |
823 state.db.services.getById('wordpress').set('is_subordinate', true); | |
824 client.send(Y.JSON.stringify(data)); | |
825 } | |
826 // Generate the services base data then execute the test. | |
827 generateServices(removeUnits); | |
828 }); | |
829 | |
830 it('can get a service', function(done) { | |
831 generateServices(function(data) { | |
832 // Post deploy of wordpress so we should be able to | |
833 // pull its data. | |
834 var op = { | |
835 op: 'get_service', | |
836 service_name: 'wordpress', | |
837 request_id: 99 | |
838 }; | |
839 client.onmessage = function(received) { | |
840 var parsed = Y.JSON.parse(received.data); | |
841 var service = parsed.result; | |
842 assert.equal(service.name, 'wordpress'); | |
843 // Error should be undefined. | |
844 done(received.error); | |
845 }; | |
846 client.send(Y.JSON.stringify(op)); | |
847 }); | |
848 }); | |
849 | |
850 it('can destroy a service', function(done) { | |
851 generateServices(function(data) { | |
852 // Post deploy of wordpress so we should be able to | |
853 // destroy it. | |
854 var op = { | |
855 op: 'destroy_service', | |
856 service_name: 'wordpress', | |
857 request_id: 99 | |
858 }; | |
859 client.onmessage = function(received) { | |
860 var parsed = Y.JSON.parse(received.data); | |
861 assert.equal(parsed.result, 'wordpress'); | |
862 // Error should be undefined. | |
863 done(received.error); | |
864 }; | |
865 client.send(Y.JSON.stringify(op)); | |
866 }); | |
867 }); | |
868 | |
869 it('can destroy a service (integration)', function(done) { | |
870 function destroyService(rec) { | |
871 function localCb(rec2) { | |
872 assert.equal(rec2.result, 'kumquat'); | |
873 var service = state.db.services.getById('kumquat'); | |
874 assert.isNull(service); | |
875 done(); | |
876 } | |
877 var result = env.destroy_service(rec.service_name, localCb); | |
878 } | |
879 generateAndExposeIntegrationService(destroyService); | |
880 }); | |
881 | |
882 it('can get a charm', function(done) { | |
883 generateServices(function(data) { | |
884 // Post deploy of wordpress we should be able to | |
885 // pull its data. | |
886 var op = { | |
887 op: 'get_charm', | |
888 charm_url: 'cs:wordpress', | |
889 request_id: 99 | |
890 }; | |
891 client.onmessage = function(received) { | |
892 var parsed = Y.JSON.parse(received.data); | |
893 var charm = parsed.result; | |
894 assert.equal(charm.name, 'wordpress'); | |
895 // Error should be undefined. | |
896 done(received.error); | |
897 }; | |
898 client.send(Y.JSON.stringify(op)); | |
899 }); | |
900 }); | |
901 | |
902 it('can set service config', function(done) { | |
903 generateServices(function(data) { | |
904 // Post deploy of wordpress we should be able to | |
905 // pull its data. | |
906 var op = { | |
907 op: 'set_config', | |
908 service_name: 'wordpress', | |
909 config: {'blog-title': 'Inimical'}, | |
910 request_id: 99 | |
911 }; | |
912 client.onmessage = function(received) { | |
913 var parsed = Y.JSON.parse(received.data); | |
914 assert.deepEqual(parsed.result, {'blog-title': 'Inimical'}); | |
915 var service = state.db.services.getById('wordpress'); | |
916 assert.equal(service.get('config')['blog-title'], 'Inimical'); | |
917 // Error should be undefined. | |
918 done(parsed.error); | |
919 }; | |
920 client.send(Y.JSON.stringify(op)); | |
921 }); | |
922 }); | |
923 | |
924 it('can set service constraints', function(done) { | |
925 generateServices(function(data) { | |
926 // Post deploy of wordpress we should be able to | |
927 // pull its data. | |
928 var op = { | |
929 op: 'set_constraints', | |
930 service_name: 'wordpress', | |
931 constraints: ['cpu=2', 'mem=128'], | |
932 request_id: 99 | |
933 }; | |
934 client.onmessage = function(received) { | |
935 var service = state.db.services.getById('wordpress'); | |
936 var constraints = service.get('constraints'); | |
937 assert.equal(constraints.cpu, '2'); | |
938 assert.equal(constraints.mem, '128'); | |
939 // Error should be undefined. | |
940 done(received.error); | |
941 }; | |
942 client.send(Y.JSON.stringify(op)); | |
943 }); | |
944 }); | |
945 | |
946 it('can expose a service', function(done) { | |
947 function checkExposedService(rec) { | |
948 var data = Y.JSON.parse(rec.data), | |
949 mock = { | |
950 op: 'expose', | |
951 result: true, | |
952 service_name: 'wordpress' | |
953 }; | |
954 var service = state.db.services.getById(mock.service_name); | |
955 assert.equal(service.get('exposed'), true); | |
956 assert.equal(data.result, true); | |
957 assert.deepEqual(data, mock); | |
958 done(); | |
959 } | |
960 generateAndExposeService(checkExposedService); | |
961 }); | |
962 | |
963 it('can expose a service (integration)', function(done) { | |
964 function checkExposedService(rec) { | |
965 var service = state.db.services.getById('kumquat'); | |
966 assert.equal(service.get('exposed'), true); | |
967 assert.equal(rec.result, true); | |
968 done(); | |
969 } | |
970 generateAndExposeIntegrationService(checkExposedService); | |
971 }); | |
972 | |
973 it('fails silently when exposing an exposed service', function(done) { | |
974 function checkExposedService(rec) { | |
975 var data = Y.JSON.parse(rec.data), | |
976 service = state.db.services.getById(data.service_name), | |
977 command = { | |
978 op: 'expose', | |
979 service_name: data.service_name | |
980 }; | |
981 state.nextChanges(); | |
982 client.onmessage = function(rec) { | |
983 assert.equal(data.err, undefined); | |
984 assert.equal(service.get('exposed'), true); | |
985 assert.equal(data.result, true); | |
986 done(); | |
987 }; | |
988 client.send(Y.JSON.stringify(command)); | |
989 } | |
990 generateAndExposeService(checkExposedService); | |
991 }); | |
992 | |
993 it('fails with error when exposing an invalid service name', | |
994 function(done) { | |
995 state.deploy('cs:wordpress', function(data) { | |
996 var command = { | |
997 op: 'expose', | |
998 service_name: 'foobar' | |
999 }; | |
1000 state.nextChanges(); | |
1001 client.onmessage = function() { | |
1002 client.onmessage = function(rec) { | |
1003 var data = Y.JSON.parse(rec.data); | |
1004 assert.equal(data.result, false); | |
1005 assert.equal(data.err, | |
1006 '"foobar" is an invalid service name.'); | |
1007 done(); | |
1008 }; | |
1009 client.send(Y.JSON.stringify(command)); | |
1010 }; | |
1011 client.open(); | |
1012 }, { unitCount: 1 }); | |
1013 } | |
1014 ); | |
1015 | |
1016 it('can unexpose a service', function(done) { | |
1017 function unexposeService(rec) { | |
1018 var data = Y.JSON.parse(rec.data), | |
1019 command = { | |
1020 op: 'unexpose', | |
1021 service_name: data.service_name | |
1022 }; | |
1023 state.nextChanges(); | |
1024 client.onmessage = function(rec) { | |
1025 var data = Y.JSON.parse(rec.data), | |
1026 service = state.db.services.getById(data.service_name), | |
1027 mock = { | |
1028 op: 'unexpose', | |
1029 result: true, | |
1030 service_name: 'wordpress' | |
1031 }; | |
1032 assert.equal(service.get('exposed'), false); | |
1033 assert.deepEqual(data, mock); | |
1034 done(); | |
1035 }; | |
1036 client.send(Y.JSON.stringify(command)); | |
1037 } | |
1038 generateAndExposeService(unexposeService); | |
1039 }); | |
1040 | |
1041 it('can unexpose a service (integration)', function(done) { | |
1042 function unexposeService(rec) { | |
1043 function localCb(rec) { | |
1044 var service = state.db.services.getById('kumquat'); | |
1045 assert.equal(service.get('exposed'), false); | |
1046 assert.equal(rec.result, true); | |
1047 done(); | |
1048 } | |
1049 env.unexpose(rec.service_name, localCb); | |
1050 } | |
1051 generateAndExposeIntegrationService(unexposeService); | |
1052 }); | |
1053 | |
1054 it('fails silently when unexposing a not exposed service', | |
1055 function(done) { | |
1056 state.deploy('cs:wordpress', function(data) { | |
1057 var command = { | |
1058 op: 'unexpose', | |
1059 service_name: data.service.get('name') | |
1060 }; | |
1061 state.nextChanges(); | |
1062 client.onmessage = function() { | |
1063 client.onmessage = function(rec) { | |
1064 var data = Y.JSON.parse(rec.data), | |
1065 service = state.db.services.getById(data.service_name); | |
1066 assert.equal(service.get('exposed'), false); | |
1067 assert.equal(data.result, true); | |
1068 assert.equal(data.err, undefined); | |
1069 done(); | |
1070 }; | |
1071 client.send(Y.JSON.stringify(command)); | |
1072 }; | |
1073 client.open(); | |
1074 }, { unitCount: 1 }); | |
1075 } | |
1076 ); | |
1077 | |
1078 it('fails with error when unexposing an invalid service name', | |
1079 function(done) { | |
1080 function unexposeService(rec) { | |
1081 var data = Y.JSON.parse(rec.data), | |
1082 command = { | |
1083 op: 'unexpose', | |
1084 service_name: 'foobar' | |
1085 }; | |
1086 state.nextChanges(); | |
1087 client.onmessage = function(rec) { | |
1088 var data = Y.JSON.parse(rec.data); | |
1089 assert.equal(data.result, false); | |
1090 assert.equal(data.err, '"foobar" is an invalid service name.'); | |
1091 done(); | |
1092 }; | |
1093 client.send(Y.JSON.stringify(command)); | |
1094 } | |
1095 generateAndExposeService(unexposeService); | |
1096 } | |
1097 ); | |
1098 | |
1099 it('can add a relation', function(done) { | |
1100 function localCb() { | |
1101 state.deploy('cs:mysql', function(service) { | |
1102 var data = { | |
1103 op: 'add_relation', | |
1104 endpoint_a: 'wordpress:db', | |
1105 endpoint_b: 'mysql:db' | |
1106 }; | |
1107 client.onmessage = function(rec) { | |
1108 var data = Y.JSON.parse(rec.data), | |
1109 mock = { | |
1110 endpoint_a: 'wordpress:db', | |
1111 endpoint_b: 'mysql:db', | |
1112 op: 'add_relation', | |
1113 result: { | |
1114 id: 'relation-0', | |
1115 'interface': 'mysql', | |
1116 scope: 'global', | |
1117 endpoints: [ | |
1118 {wordpress: {name: 'db'}}, | |
1119 {mysql: {name: 'db'}} | |
1120 ] | |
1121 } | |
1122 }; | |
1123 | |
1124 assert.equal(data.err, undefined); | |
1125 assert.equal(typeof data.result, 'object'); | |
1126 assert.deepEqual(data, mock); | |
1127 done(); | |
1128 }; | |
1129 client.send(Y.JSON.stringify(data)); | |
1130 }); | |
1131 } | |
1132 generateServices(localCb); | |
1133 }); | |
1134 | |
1135 it('can add a relation (integration)', function(done) { | |
1136 function addRelation() { | |
1137 function localCb(rec) { | |
1138 var mock = { | |
1139 endpoint_a: 'kumquat:db', | |
1140 endpoint_b: 'mysql:db', | |
1141 op: 'add_relation', | |
1142 request_id: rec.request_id, | |
1143 result: { | |
1144 id: 'relation-0', | |
1145 'interface': 'mysql', | |
1146 scope: 'global', | |
1147 request_id: rec.request_id, | |
1148 endpoints: [ | |
1149 {kumquat: {name: 'db'}}, | |
1150 {mysql: {name: 'db'}} | |
1151 ] | |
1152 } | |
1153 }; | |
1154 | |
1155 assert.equal(rec.err, undefined); | |
1156 assert.equal(typeof rec.result, 'object'); | |
1157 assert.deepEqual(rec.details[0], mock); | |
1158 done(); | |
1159 } | |
1160 var endpointA = [ | |
1161 'kumquat', | |
1162 { name: 'db', | |
1163 role: 'client' } | |
1164 ]; | |
1165 var endpointB = [ | |
1166 'mysql', | |
1167 { name: 'db', | |
1168 role: 'server' } | |
1169 ]; | |
1170 env.add_relation(endpointA, endpointB, localCb); | |
1171 } | |
1172 generateIntegrationServices(function() { | |
1173 env.deploy('cs:mysql', undefined, undefined, undefined, 1, addRelation); | |
1174 }); | |
1175 }); | |
1176 | |
1177 it('is able to add a relation with a subordinate service', function(done) { | |
1178 function localCb() { | |
1179 state.deploy('cs:puppet', function(service) { | |
1180 var data = { | |
1181 op: 'add_relation', | |
1182 endpoint_a: 'wordpress:juju-info', | |
1183 endpoint_b: 'puppet:juju-info' | |
1184 }; | |
1185 | |
1186 client.onmessage = function(rec) { | |
1187 var data = Y.JSON.parse(rec.data), | |
1188 mock = { | |
1189 endpoint_a: 'wordpress:juju-info', | |
1190 endpoint_b: 'puppet:juju-info', | |
1191 op: 'add_relation', | |
1192 result: { | |
1193 id: 'relation-0', | |
1194 'interface': 'juju-info', | |
1195 scope: 'container', | |
1196 endpoints: [ | |
1197 {puppet: {name: 'juju-info'}}, | |
1198 {wordpress: {name: 'juju-info'}} | |
1199 ] | |
1200 } | |
1201 }; | |
1202 assert.equal(data.err, undefined); | |
1203 assert.equal(typeof data.result, 'object'); | |
1204 assert.deepEqual(data, mock); | |
1205 done(); | |
1206 }; | |
1207 client.send(Y.JSON.stringify(data)); | |
1208 }); | |
1209 } | |
1210 generateServices(localCb); | |
1211 }); | |
1212 | |
1213 it('throws an error if only one endpoint is supplied', function(done) { | |
1214 function localCb() { | |
1215 var data = { | |
1216 op: 'add_relation', | |
1217 endpoint_a: 'wordpress:db' | |
1218 }; | |
1219 state.nextChanges(); | |
1220 client.onmessage = function(rec) { | |
1221 var data = Y.JSON.parse(rec.data); | |
1222 assert(data.err, 'Two endpoints required to set up relation.'); | |
1223 done(); | |
1224 }; | |
1225 client.send(Y.JSON.stringify(data)); | |
1226 } | |
1227 generateServices(localCb); | |
1228 }); | |
1229 | |
1230 it('throws an error if endpoints are not relatable', function(done) { | |
1231 function localCb() { | |
1232 var data = { | |
1233 op: 'add_relation', | |
1234 endpoint_a: 'wordpress:db', | |
1235 endpoint_b: 'mysql:foo' | |
1236 }; | |
1237 state.nextChanges(); | |
1238 client.onmessage = function(rec) { | |
1239 var data = Y.JSON.parse(rec.data); | |
1240 assert(data.err, 'No matching interfaces.'); | |
1241 done(); | |
1242 }; | |
1243 client.send(Y.JSON.stringify(data)); | |
1244 } | |
1245 generateServices(localCb); | |
1246 }); | |
1247 | |
1248 it('can remove a relation', function(done) { | |
1249 generateAndRelateServices( | |
1250 ['cs:wordpress', 'cs:mysql'], | |
1251 ['wordpress:db', 'mysql:db'], | |
1252 ['wordpress:db', 'mysql:db'], | |
1253 {result: true, endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'}, | |
1254 done); | |
1255 }); | |
1256 | |
1257 it('can remove a relation (integration)', function(done) { | |
1258 var endpoints = [ | |
1259 ['kumquat', | |
1260 { name: 'db', | |
1261 role: 'client' }], | |
1262 ['mysql', | |
1263 { name: 'db', | |
1264 role: 'server' }] | |
1265 ]; | |
1266 env.after('defaultSeriesChange', function() { | |
1267 function localCb(result) { | |
1268 var mock = { | |
1269 endpoint_a: 'kumquat:db', | |
1270 endpoint_b: 'mysql:db', | |
1271 op: 'remove_relation', | |
1272 request_id: 4, | |
1273 result: true | |
1274 }; | |
1275 assert.deepEqual(result.details[0], mock); | |
1276 done(); | |
1277 } | |
1278 env.deploy( | |
1279 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, function() { | |
1280 env.deploy('cs:mysql', null, null, null, 1, function() { | |
1281 env.add_relation(endpoints[0], endpoints[1], function() { | |
1282 env.remove_relation(endpoints[0], endpoints[1], localCb); | |
1283 }); | |
1284 }); | |
1285 } | |
1286 ); | |
1287 }); | |
1288 env.connect(); | |
1289 }); | |
1290 | |
1291 it('throws an error if the charms do not exist', function(done) { | |
1292 generateAndRelateServices( | |
1293 ['cs:wordpress', 'cs:mysql'], | |
1294 ['wordpress:db', 'mysql:db'], | |
1295 ['no_such', 'charms'], | |
1296 {err: 'Charm not loaded.', | |
1297 endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'}, | |
1298 done); | |
1299 }); | |
1300 | |
1301 it('throws an error if the relationship does not exist', function(done) { | |
1302 generateAndRelateServices( | |
1303 ['cs:wordpress', 'cs:mysql'], | |
1304 null, | |
1305 ['wordpress:db', 'mysql:db'], | |
1306 {err: 'Relationship does not exist', | |
1307 endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'}, | |
1308 done); | |
1309 }); | |
1310 | |
1311 describe('Sandbox Annotations', function() { | |
1312 | |
1313 it('should handle service annotation updates', function(done) { | |
1314 generateServices(function(data) { | |
1315 // Post deploy of wordpress we should be able to | |
1316 // pull its data. | |
1317 var op = { | |
1318 op: 'update_annotations', | |
1319 entity: 'wordpress', | |
1320 data: {'foo': 'bar'}, | |
1321 request_id: 99 | |
1322 }; | |
1323 client.onmessage = function(received) { | |
1324 var service = state.db.services.getById('wordpress'); | |
1325 var annotations = service.get('annotations'); | |
1326 assert.equal(annotations.foo, 'bar'); | |
1327 // Validate that annotations appear in the delta stream. | |
1328 client.onmessage = function(delta) { | |
1329 delta = Y.JSON.parse(delta.data); | |
1330 assert.equal(delta.op, 'delta'); | |
1331 var serviceChange = Y.Array.find(delta.result, function(change) { | |
1332 return change[0] === 'service'; | |
1333 }); | |
1334 assert.equal(serviceChange[0], 'service'); | |
1335 assert.equal(serviceChange[1], 'change'); | |
1336 assert.deepEqual(serviceChange[2].annotations, {'foo': 'bar'}); | |
1337 // Error should be undefined. | |
1338 done(received.error); | |
1339 }; | |
1340 juju.sendDelta(); | |
1341 }; | |
1342 client.open(); | |
1343 client.send(Y.JSON.stringify(op)); | |
1344 }); | |
1345 }); | |
1346 | |
1347 it('should handle environment annotation updates', function(done) { | |
1348 generateServices(function(data) { | |
1349 // We only deploy a service here to reuse the env connect/setup | |
1350 // code. | |
1351 // Post deploy of wordpress we should be able to | |
1352 // pull env data. | |
1353 client.onmessage = function(received) { | |
1354 var env = state.db.environment; | |
1355 var annotations = env.get('annotations'); | |
1356 assert.equal(annotations.foo, 'bar'); | |
1357 // Validate that annotations appear in the delta stream. | |
1358 client.onmessage = function(delta) { | |
1359 delta = Y.JSON.parse(delta.data); | |
1360 assert.equal(delta.op, 'delta'); | |
1361 var envChange = Y.Array.find(delta.result, function(change) { | |
1362 return change[0] === 'annotations'; | |
1363 }); | |
1364 assert.equal(envChange[1], 'change'); | |
1365 assert.deepEqual(envChange[2], {'foo': 'bar'}); | |
1366 done(); | |
1367 }; | |
1368 juju.sendDelta(); | |
1369 }; | |
1370 client.open(); | |
1371 client.send(Y.JSON.stringify({ | |
1372 op: 'update_annotations', | |
1373 entity: 'env', | |
1374 data: {'foo': 'bar'}, | |
1375 request_id: 99 | |
1376 })); | |
1377 }); | |
1378 }); | |
1379 | |
1380 it('should handle unit annotation updates', function(done) { | |
1381 generateServices(function(data) { | |
1382 // Post deploy of wordpress we should be able to | |
1383 // pull its data. | |
1384 var op = { | |
1385 op: 'update_annotations', | |
1386 entity: 'wordpress/0', | |
1387 data: {'foo': 'bar'}, | |
1388 request_id: 99 | |
1389 }; | |
1390 client.onmessage = function(received) { | |
1391 var unit = state.db.units.getById('wordpress/0'); | |
1392 var annotations = unit.annotations; | |
1393 assert.equal(annotations.foo, 'bar'); | |
1394 // Error should be undefined. | |
1395 done(received.error); | |
1396 }; | |
1397 client.open(); | |
1398 client.send(Y.JSON.stringify(op)); | |
1399 }); | |
1400 }); | |
1401 | |
1402 }); | |
1403 | |
1404 it('should allow unit resolved to be called', function(done) { | |
1405 generateServices(function(data) { | |
1406 // Post deploy of wordpress we should be able to | |
1407 // pull its data. | |
1408 var op = { | |
1409 op: 'resolved', | |
1410 unit_name: 'wordpress/0', | |
1411 request_id: 99 | |
1412 }; | |
1413 client.onmessage = function(received) { | |
1414 var parsed = Y.JSON.parse(received.data); | |
1415 assert.equal(parsed.result, true); | |
1416 done(parsed.error); | |
1417 }; | |
1418 client.open(); | |
1419 client.send(Y.JSON.stringify(op)); | |
1420 }); | |
1421 }); | |
1422 | |
1423 /** | |
1424 * Utility method to turn _some_ callback | |
1425 * styled async methods into Promises. | |
1426 * It does this by supplying a simple | |
1427 * adaptor that can handle {error:...} | |
1428 * and {result: ... } returns. | |
1429 * | |
1430 * This callback is appended to any calling arguments | |
1431 * | |
1432 * @method promise | |
1433 * @param {Object} context Calling context. | |
1434 * @param {String} methodName name of method on context to invoke. | |
1435 * @param {Arguments} arguments Additional arguments passed | |
1436 * to resolved method. | |
1437 * @return {Promise} a Y.Promise object. | |
1438 */ | |
1439 function promise(context, methodName) { | |
1440 var slice = Array.prototype.slice; | |
1441 var args = slice.call(arguments, 2); | |
1442 var method = context[methodName]; | |
1443 | |
1444 return Y.Promise(function(resolve, reject) { | |
1445 var resultHandler = function(result) { | |
1446 if (result.err || result.error) { | |
1447 reject(result.err || result.error); | |
1448 } else { | |
1449 resolve(result); | |
1450 } | |
1451 }; | |
1452 | |
1453 args.push(resultHandler); | |
1454 var result = method.apply(context, args); | |
1455 if (result !== undefined) { | |
1456 // The method returned right away. | |
1457 return resultHandler(result); | |
1458 } | |
1459 }); | |
1460 } | |
1461 | |
1462 it('should support export', function(done) { | |
1463 client.open(); | |
1464 promise(state, 'deploy', 'cs:wordpress') | |
1465 .then(promise(state, 'deploy', 'cs:mysql')) | |
1466 .then(promise(state, 'addRelation', 'wordpress:db', 'mysql:db')) | |
1467 .then(function() { | |
1468 client.onmessage = function(result) { | |
1469 var data = Y.JSON.parse(result.data).result; | |
1470 assert.equal(data.services[0].name, 'wordpress'); | |
1471 done(); | |
1472 }; | |
1473 client.send(Y.JSON.stringify({op: 'exportEnvironment'})); | |
1474 }); | |
1475 }); | |
1476 | |
1477 it('should support import', function(done) { | |
1478 var fixture = utils.loadFixture('data/sample-fakebackend.json', false); | |
1479 | |
1480 client.onmessage = function() { | |
1481 client.onmessage = function(result) { | |
1482 var data = Y.JSON.parse(result.data).result; | |
1483 assert.isTrue(data); | |
1484 | |
1485 // Verify that we can now find an expected entry | |
1486 // in the database. | |
1487 assert.isNotNull(state.db.services.getById('wordpress')); | |
1488 | |
1489 var changes = state.nextChanges(); | |
1490 // Validate the delta includes imported services. | |
1491 assert.include(changes.services, 'wordpress'); | |
1492 assert.include(changes.services, 'mysql'); | |
1493 // validate relation was added/updated. | |
1494 assert.include(changes.relations, 'relation-0'); | |
1495 done(); | |
1496 }; | |
1497 client.send(Y.JSON.stringify({op: 'importEnvironment', | |
1498 envData: fixture})); | |
1499 }; | |
1500 client.open(); | |
1501 }); | |
1502 | |
1503 }); | |
1504 | |
1505 | |
1506 describe('sandbox.GoJujuAPI', function() { | |
1507 var requires = [ | |
1508 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-go', | |
1509 'juju-models', 'promise']; | |
1510 var Y, sandboxModule, ClientConnection, environmentsModule, state, juju, | |
1511 client, env, utils; | |
1512 | |
1513 before(function(done) { | |
1514 Y = YUI(GlobalConfig).use(requires, function(Y) { | |
1515 sandboxModule = Y.namespace('juju.environments.sandbox'); | |
1516 environmentsModule = Y.namespace('juju.environments'); | |
1517 utils = Y.namespace('juju-tests.utils'); | |
1518 // A global variable required for testing. | |
1519 window.flags = {}; | |
1520 done(); | |
1521 }); | |
1522 }); | |
1523 | |
1524 beforeEach(function() { | |
1525 state = utils.makeFakeBackendWithCharmStore(); | |
1526 juju = new sandboxModule.GoJujuAPI({state: state}); | |
1527 client = new sandboxModule.ClientConnection({juju: juju}); | |
1528 env = new environmentsModule.GoEnvironment({conn: client}); | |
1529 }); | |
1530 | |
1531 afterEach(function() { | |
1532 env.destroy(); | |
1533 client.destroy(); | |
1534 juju.destroy(); | |
1535 state.destroy(); | |
1536 }); | |
1537 | |
1538 after(function() { | |
1539 delete window.flags; | |
1540 }); | |
1541 | |
1542 it('opens successfully.', function() { | |
1543 assert.isFalse(juju.connected); | |
1544 assert.isUndefined(juju.get('client')); | |
1545 client.open(); | |
1546 assert.isTrue(juju.connected); | |
1547 assert.strictEqual(juju.get('client'), client); | |
1548 }); | |
1549 | |
1550 it('ignores "open" when already open to same client.', function() { | |
1551 client.receive = function() { | |
1552 assert.ok(false, 'The receive method should not be called.'); | |
1553 }; | |
1554 // Whitebox test: duplicate "open" state. | |
1555 juju.connected = true; | |
1556 juju.set('client', client); | |
1557 // This is effectively a re-open. | |
1558 client.open(); | |
1559 // The assert.ok above is the verification. | |
1560 }); | |
1561 | |
1562 it('refuses to open if already open to another client.', function() { | |
1563 // This is a simple way to make sure that we don't leave multiple | |
1564 // setInterval calls running. If for some reason we want more | |
1565 // simultaneous clients, that's fine, though that will require | |
1566 // reworking the delta code generally. | |
1567 juju.connected = true; | |
1568 juju.set('client', {receive: function() { | |
1569 assert.ok(false, 'The receive method should not have been called.'); | |
1570 }}); | |
1571 assert.throws( | |
1572 client.open.bind(client), | |
1573 'INVALID_STATE_ERR : Connection is open to another client.'); | |
1574 }); | |
1575 | |
1576 it('closes successfully.', function() { | |
1577 client.open(); | |
1578 assert.isTrue(juju.connected); | |
1579 assert.notEqual(juju.get('client'), undefined); | |
1580 client.close(); | |
1581 assert.isFalse(juju.connected); | |
1582 assert.isUndefined(juju.get('client')); | |
1583 }); | |
1584 | |
1585 it('ignores "close" when already closed.', function() { | |
1586 // This simply shows that we do not raise an error. | |
1587 juju.close(); | |
1588 }); | |
1589 | |
1590 it('can dispatch on received information.', function(done) { | |
1591 var data = {Type: 'TheType', Request: 'TheRequest'}; | |
1592 juju.handleTheTypeTheRequest = function(received) { | |
1593 assert.notStrictEqual(received, data); | |
1594 assert.deepEqual(received, data); | |
1595 done(); | |
1596 }; | |
1597 client.open(); | |
1598 client.send(Y.JSON.stringify(data)); | |
1599 }); | |
1600 | |
1601 it('refuses to dispatch when closed.', function() { | |
1602 assert.throws( | |
1603 juju.receive.bind(juju, {}), | |
1604 'INVALID_STATE_ERR : Connection is closed.' | |
1605 ); | |
1606 }); | |
1607 | |
1608 it('can log in.', function(done) { | |
1609 // See FakeBackend's authorizedUsers for these default authentication | |
1610 // values. | |
1611 var data = { | |
1612 Type: 'Admin', | |
1613 Request: 'Login', | |
1614 Params: { | |
1615 AuthTag: 'admin', | |
1616 Password: 'password' | |
1617 }, | |
1618 RequestId: 42 | |
1619 }; | |
1620 client.onmessage = function(received) { | |
1621 // Add in the error indicator so the deepEqual is comparing apples to | |
1622 // apples. | |
1623 data.Error = false; | |
1624 assert.deepEqual(Y.JSON.parse(received.data), data); | |
1625 assert.isTrue(state.get('authenticated')); | |
1626 done(); | |
1627 }; | |
1628 state.logout(); | |
1629 assert.isFalse(state.get('authenticated')); | |
1630 client.open(); | |
1631 client.send(Y.JSON.stringify(data)); | |
1632 }); | |
1633 | |
1634 it('can log in (environment integration).', function(done) { | |
1635 state.logout(); | |
1636 env.after('login', function() { | |
1637 assert.isTrue(env.userIsAuthenticated); | |
1638 done(); | |
1639 }); | |
1640 env.connect(); | |
1641 env.setCredentials({user: 'admin', password: 'password'}); | |
1642 env.login(); | |
1643 }); | |
1644 | |
1645 it('can deploy.', function(done) { | |
1646 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
1647 var data = { | |
1648 Type: 'Client', | |
1649 Request: 'ServiceDeploy', | |
1650 Params: { | |
1651 CharmUrl: 'cs:wordpress', | |
1652 ServiceName: 'kumquat', | |
1653 ConfigYAML: 'funny: business', | |
1654 NumUnits: 2 | |
1655 }, | |
1656 RequestId: 42 | |
1657 }; | |
1658 client.onmessage = function(received) { | |
1659 var receivedData = Y.JSON.parse(received.data); | |
1660 assert.equal(receivedData.RequestId, data.RequestId); | |
1661 assert.isUndefined(receivedData.Error); | |
1662 assert.isObject( | |
1663 state.db.charms.getById('cs:precise/wordpress-10')); | |
1664 var service = state.db.services.getById('kumquat'); | |
1665 assert.isObject(service); | |
1666 assert.equal(service.get('charm'), 'cs:precise/wordpress-10'); | |
1667 assert.deepEqual(service.get('config'), {funny: 'business'}); | |
1668 var units = state.db.units.get_units_for_service(service); | |
1669 assert.lengthOf(units, 2); | |
1670 done(); | |
1671 }; | |
1672 client.open(); | |
1673 client.send(Y.JSON.stringify(data)); | |
1674 }); | |
1675 | |
1676 it('can deploy (environment integration).', function() { | |
1677 env.connect(); | |
1678 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
1679 var callback = function(result) { | |
1680 assert.isUndefined(result.err); | |
1681 assert.equal(result.charm_url, 'cs:wordpress'); | |
1682 var service = state.db.services.getById('kumquat'); | |
1683 assert.equal(service.get('charm'), 'cs:precise/wordpress-10'); | |
1684 assert.deepEqual(service.get('config'), {llama: 'pajama'}); | |
1685 }; | |
1686 env.deploy( | |
1687 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback); | |
1688 }); | |
1689 | |
1690 it('can communicate errors after attempting to deploy', function(done) { | |
1691 env.connect(); | |
1692 state.deploy('cs:wordpress', function() {}); | |
1693 var callback = function(result) { | |
1694 assert.equal( | |
1695 result.err, 'A service with this name already exists.'); | |
1696 done(); | |
1697 }; | |
1698 env.deploy('cs:wordpress', undefined, undefined, undefined, 1, | |
1699 callback); | |
1700 }); | |
1701 | |
1702 it('can set a charm.', function(done) { | |
1703 state.deploy('cs:wordpress', function() {}); | |
1704 var data = { | |
1705 Type: 'Client', | |
1706 Request: 'ServiceSetCharm', | |
1707 Params: { | |
1708 ServiceName: 'wordpress', | |
1709 CharmUrl: 'cs:precise/mediawiki-6', | |
1710 Force: false | |
1711 }, | |
1712 RequestId: 42 | |
1713 }; | |
1714 client.onmessage = function(received) { | |
1715 var receivedData = Y.JSON.parse(received.data); | |
1716 assert.isUndefined(receivedData.err); | |
1717 var service = state.db.services.getById('wordpress'); | |
1718 assert.equal(service.get('charm'), 'cs:precise/mediawiki-6'); | |
1719 done(); | |
1720 }; | |
1721 client.open(); | |
1722 client.send(Y.JSON.stringify(data)); | |
1723 }); | |
1724 | |
1725 it('can set a charm (environment integration).', function(done) { | |
1726 env.connect(); | |
1727 state.deploy('cs:wordpress', function() {}); | |
1728 var callback = function(result) { | |
1729 assert.isUndefined(result.err); | |
1730 var service = state.db.services.getById('wordpress'); | |
1731 assert.equal(service.get('charm'), 'cs:precise/mediawiki-6'); | |
1732 done(); | |
1733 }; | |
1734 env.setCharm('wordpress', 'cs:precise/mediawiki-6', false, callback); | |
1735 }); | |
1736 | |
1737 /** | |
1738 Generates the services required for some tests. After the services have | |
1739 been generated it will call the supplied callback. | |
1740 | |
1741 This interacts directly with the fakebackend bypassing the environment. | |
1742 The test "can add additional units" tests this code directly so as long | |
1743 as it passes you can consider this method valid. | |
1744 | |
1745 @method generateServices | |
1746 @param {Function} callback The callback to call after the services have | |
1747 been generated. | |
1748 */ | |
1749 function generateServices(callback) { | |
1750 state.deploy('cs:wordpress', function(service) { | |
1751 var data = { | |
1752 Type: 'Client', | |
1753 Request: 'AddServiceUnits', | |
1754 Params: { | |
1755 ServiceName: 'wordpress', | |
1756 NumUnits: 2 | |
1757 } | |
1758 }; | |
1759 state.nextChanges(); | |
1760 client.onmessage = function(received) { | |
1761 // After done generating the services | |
1762 callback(received); | |
1763 }; | |
1764 client.open(); | |
1765 client.send(Y.JSON.stringify(data)); | |
1766 }); | |
1767 } | |
1768 | |
1769 /** | |
1770 Same as generateServices but uses the environment integration methods. | |
1771 Should be considered valid if "can add additional units (integration)" | |
1772 test passes. | |
1773 | |
1774 @method generateIntegrationServices | |
1775 @param {Function} callback The callback to call after the services have | |
1776 been generated. | |
1777 */ | |
1778 function generateIntegrationServices(callback) { | |
1779 var localCb = function(result) { | |
1780 env.add_unit('kumquat', 2, function(data) { | |
1781 // After finished generating integrated services. | |
1782 callback(data); | |
1783 }); | |
1784 }; | |
1785 env.connect(); | |
1786 env.deploy( | |
1787 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb); | |
1788 } | |
1789 | |
1790 /** | |
1791 Generates the services and then exposes them for the un/expose tests. | |
1792 After they have been exposed it calls the supplied callback. | |
1793 | |
1794 This interacts directly with the fakebackend bypassing the environment and | |
1795 should be considered valid if "can expose a service" test passes. | |
1796 | |
1797 @method generateAndExposeService | |
1798 @param {Function} callback The callback to call after the services have | |
1799 been generated. | |
1800 */ | |
1801 function generateAndExposeService(callback) { | |
1802 state.deploy('cs:wordpress', function(data) { | |
1803 var command = { | |
1804 Type: 'Client', | |
1805 Request: 'ServiceExpose', | |
1806 Params: {ServiceName: data.service.get('name')} | |
1807 }; | |
1808 state.nextChanges(); | |
1809 client.onmessage = function(rec) { | |
1810 callback(rec); | |
1811 }; | |
1812 client.open(); | |
1813 client.send(Y.JSON.stringify(command)); | |
1814 }, { unitCount: 1 }); | |
1815 } | |
1816 | |
1817 /** | |
1818 Same as generateAndExposeService but uses the environment integration | |
1819 methods. Should be considered valid if "can expose a service | |
1820 (integration)" test passes. | |
1821 | |
1822 @method generateAndExposeIntegrationService | |
1823 @param {Function} callback The callback to call after the services have | |
1824 been generated. | |
1825 */ | |
1826 function generateAndExposeIntegrationService(callback) { | |
1827 var localCb = function(result) { | |
1828 env.expose(result.service_name, function(rec) { | |
1829 callback(rec); | |
1830 }); | |
1831 }; | |
1832 env.connect(); | |
1833 env.deploy( | |
1834 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb); | |
1835 } | |
1836 | |
1837 it('can add additional units', function(done) { | |
1838 function testForAddedUnits(received) { | |
1839 var service = state.db.services.getById('wordpress'), | |
1840 units = state.db.units.get_units_for_service(service), | |
1841 data = Y.JSON.parse(received.data), | |
1842 mock = { | |
1843 Response: { | |
1844 Units: ['wordpress/1', 'wordpress/2'] | |
1845 } | |
1846 }; | |
1847 // Do we have enough total units? | |
1848 assert.lengthOf(units, 3); | |
1849 // Does the response object contain the proper data | |
1850 assert.deepEqual(data, mock); | |
1851 // Error is undefined | |
1852 assert.isUndefined(data.Error); | |
1853 done(); | |
1854 } | |
1855 // Generate the default services and add units | |
1856 generateServices(testForAddedUnits); | |
1857 }); | |
1858 | |
1859 it('throws an error when adding units to an invalid service', | |
1860 function(done) { | |
1861 state.deploy('cs:wordpress', function(service) { | |
1862 var data = { | |
1863 Type: 'Client', | |
1864 Request: 'AddServiceUnits', | |
1865 Params: { | |
1866 ServiceName: 'noservice', | |
1867 NumUnits: 2 | |
1868 } | |
1869 }; | |
1870 state.nextChanges(); | |
1871 client.onmessage = function() { | |
1872 client.onmessage = function(received) { | |
1873 var data = Y.JSON.parse(received.data); | |
1874 | |
1875 // If there is no error data.err will be undefined | |
1876 assert.equal(true, !!data.Error); | |
1877 done(); | |
1878 }; | |
1879 client.send(Y.JSON.stringify(data)); | |
1880 }; | |
1881 client.open(); | |
1882 client.onmessage(); | |
1883 }); | |
1884 } | |
1885 ); | |
1886 | |
1887 it('can add additional units (integration)', function(done) { | |
1888 function testForAddedUnits(data) { | |
1889 var service = state.db.services.getById('kumquat'), | |
1890 units = state.db.units.get_units_for_service(service); | |
1891 assert.lengthOf(units, 3); | |
1892 done(); | |
1893 } | |
1894 generateIntegrationServices(testForAddedUnits); | |
1895 }); | |
1896 | |
1897 it('can expose a service', function(done) { | |
1898 function checkExposedService(rec) { | |
1899 var serviceName = 'wordpress'; | |
1900 var data = Y.JSON.parse(rec.data), | |
1901 mock = {Response: {}}; | |
1902 var service = state.db.services.getById(serviceName); | |
1903 assert.equal(service.get('exposed'), true); | |
1904 assert.deepEqual(data, mock); | |
1905 done(); | |
1906 } | |
1907 generateAndExposeService(checkExposedService); | |
1908 }); | |
1909 | |
1910 it('can expose a service (integration)', function(done) { | |
1911 function checkExposedService(rec) { | |
1912 var service = state.db.services.getById('kumquat'); | |
1913 assert.equal(service.get('exposed'), true); | |
1914 // The Go API does not set a result value. That is OK as | |
1915 // it is never used. | |
1916 assert.isUndefined(rec.result); | |
1917 done(); | |
1918 } | |
1919 generateAndExposeIntegrationService(checkExposedService); | |
1920 }); | |
1921 | |
1922 it('fails silently when exposing an exposed service', function(done) { | |
1923 function checkExposedService(rec) { | |
1924 var service_name = 'wordpress', | |
1925 data = Y.JSON.parse(rec.data), | |
1926 service = state.db.services.getById(service_name), | |
1927 command = { | |
1928 Type: 'Client', | |
1929 Request: 'ServiceExpose', | |
1930 Params: {ServiceName: service_name} | |
1931 }; | |
1932 state.nextChanges(); | |
1933 client.onmessage = function(rec) { | |
1934 assert.equal(data.err, undefined); | |
1935 assert.equal(service.get('exposed'), true); | |
1936 done(); | |
1937 }; | |
1938 client.send(Y.JSON.stringify(command)); | |
1939 } | |
1940 generateAndExposeService(checkExposedService); | |
1941 }); | |
1942 | |
1943 it('fails with error when exposing an invalid service name', | |
1944 function(done) { | |
1945 state.deploy('cs:wordpress', function(data) { | |
1946 var command = { | |
1947 Type: 'Client', | |
1948 Request: 'ServiceExpose', | |
1949 Params: {ServiceName: 'foobar'} | |
1950 }; | |
1951 state.nextChanges(); | |
1952 client.onmessage = function(rec) { | |
1953 var data = Y.JSON.parse(rec.data); | |
1954 assert.equal(data.Error, | |
1955 '"foobar" is an invalid service name.'); | |
1956 done(); | |
1957 }; | |
1958 client.open(); | |
1959 client.send(Y.JSON.stringify(command)); | |
1960 }, { unitCount: 1 }); | |
1961 } | |
1962 ); | |
1963 | |
1964 it('can unexpose a service', function(done) { | |
1965 function unexposeService(rec) { | |
1966 var service_name = 'wordpress', | |
1967 data = Y.JSON.parse(rec.data), | |
1968 command = { | |
1969 Type: 'Client', | |
1970 Request: 'ServiceUnexpose', | |
1971 Params: {ServiceName: service_name} | |
1972 }; | |
1973 state.nextChanges(); | |
1974 client.onmessage = function(rec) { | |
1975 var data = Y.JSON.parse(rec.data), | |
1976 service = state.db.services.getById('wordpress'), | |
1977 mock = {Response: {}}; | |
1978 assert.equal(service.get('exposed'), false); | |
1979 assert.deepEqual(data, mock); | |
1980 done(); | |
1981 }; | |
1982 client.send(Y.JSON.stringify(command)); | |
1983 } | |
1984 generateAndExposeService(unexposeService); | |
1985 }); | |
1986 | |
1987 it('can unexpose a service (integration)', function(done) { | |
1988 var service_name = 'kumquat'; | |
1989 function unexposeService(rec) { | |
1990 function localCb(rec) { | |
1991 var service = state.db.services.getById(service_name); | |
1992 assert.equal(service.get('exposed'), false); | |
1993 // No result from Go unexpose. | |
1994 assert.isUndefined(rec.result); | |
1995 done(); | |
1996 } | |
1997 env.unexpose(service_name, localCb); | |
1998 } | |
1999 generateAndExposeIntegrationService(unexposeService); | |
2000 }); | |
2001 | |
2002 it('fails silently when unexposing a not exposed service', | |
2003 function(done) { | |
2004 var service_name = 'wordpress'; | |
2005 state.deploy('cs:wordpress', function(data) { | |
2006 var command = { | |
2007 Type: 'Client', | |
2008 Request: 'ServiceUnexpose', | |
2009 Params: {ServiceName: service_name} | |
2010 }; | |
2011 state.nextChanges(); | |
2012 client.onmessage = function(rec) { | |
2013 var data = Y.JSON.parse(rec.data), | |
2014 service = state.db.services.getById(service_name); | |
2015 assert.equal(service.get('exposed'), false); | |
2016 assert.equal(data.err, undefined); | |
2017 done(); | |
2018 }; | |
2019 client.open(); | |
2020 client.send(Y.JSON.stringify(command)); | |
2021 }, { unitCount: 1 }); | |
2022 } | |
2023 ); | |
2024 | |
2025 it('fails with error when unexposing an invalid service name', | |
2026 function(done) { | |
2027 function unexposeService(rec) { | |
2028 var data = Y.JSON.parse(rec.data), | |
2029 command = { | |
2030 Type: 'Client', | |
2031 Request: 'ServiceUnexpose', | |
2032 Params: {ServiceName: 'foobar'} | |
2033 }; | |
2034 state.nextChanges(); | |
2035 client.onmessage = function(rec) { | |
2036 var data = Y.JSON.parse(rec.data); | |
2037 assert.equal(data.Error, '"foobar" is an invalid service name.'); | |
2038 done(); | |
2039 }; | |
2040 client.send(Y.JSON.stringify(command)); | |
2041 } | |
2042 generateAndExposeService(unexposeService); | |
2043 } | |
2044 ); | |
2045 | |
2046 it('can add a relation', function(done) { | |
2047 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
2048 state.deploy('cs:wordpress', function() { | |
2049 state.deploy('cs:mysql', function() { | |
2050 var data = { | |
2051 RequestId: 42, | |
2052 Type: 'Client', | |
2053 Request: 'AddRelation', | |
2054 Params: { | |
2055 Endpoints: ['wordpress:db', 'mysql:db'] | |
2056 } | |
2057 }; | |
2058 client.onmessage = function(received) { | |
2059 var recData = Y.JSON.parse(received.data); | |
2060 assert.equal(recData.RequestId, data.RequestId); | |
2061 assert.equal(recData.Error, undefined); | |
2062 var recEndpoints = recData.Response.Endpoints; | |
2063 assert.equal(recEndpoints.wordpress.Name, 'db'); | |
2064 assert.equal(recEndpoints.wordpress.Scope, 'global'); | |
2065 assert.equal(recEndpoints.mysql.Name, 'db'); | |
2066 assert.equal(recEndpoints.mysql.Scope, 'global'); | |
2067 done(); | |
2068 }; | |
2069 client.open(); | |
2070 client.send(Y.JSON.stringify(data)); | |
2071 }); | |
2072 }); | |
2073 }); | |
2074 | |
2075 it('can add a relation (integration)', function(done) { | |
2076 env.connect(); | |
2077 env.deploy('cs:wordpress', null, null, null, 1, function() { | |
2078 env.deploy('cs:mysql', null, null, null, 1, function() { | |
2079 var endpointA = ['wordpress', {name: 'db', role: 'client'}], | |
2080 endpointB = ['mysql', {name: 'db', role: 'server'}]; | |
2081 env.add_relation(endpointA, endpointB, function(recData) { | |
2082 assert.equal(recData.err, undefined); | |
2083 assert.equal(recData.endpoint_a, 'wordpress:db'); | |
2084 assert.equal(recData.endpoint_b, 'mysql:db'); | |
2085 assert.isObject(recData.result); | |
2086 done(); | |
2087 }); | |
2088 }); | |
2089 }); | |
2090 }); | |
2091 | |
2092 it('is able to add a relation with a subordinate service', function(done) { | |
2093 state.deploy('cs:wordpress', function() { | |
2094 state.deploy('cs:puppet', function(service) { | |
2095 var data = { | |
2096 RequestId: 42, | |
2097 Type: 'Client', | |
2098 Request: 'AddRelation', | |
2099 Params: { | |
2100 Endpoints: ['wordpress:juju-info', 'puppet:juju-info'] | |
2101 } | |
2102 }; | |
2103 client.onmessage = function(received) { | |
2104 var recData = Y.JSON.parse(received.data); | |
2105 assert.equal(recData.RequestId, data.RequestId); | |
2106 assert.equal(recData.Error, undefined); | |
2107 var recEndpoints = recData.Response.Endpoints; | |
2108 assert.equal(recEndpoints.wordpress.Name, 'juju-info'); | |
2109 assert.equal(recEndpoints.wordpress.Scope, 'container'); | |
2110 assert.equal(recEndpoints.puppet.Name, 'juju-info'); | |
2111 assert.equal(recEndpoints.puppet.Scope, 'container'); | |
2112 done(); | |
2113 }; | |
2114 client.open(); | |
2115 client.send(Y.JSON.stringify(data)); | |
2116 }); | |
2117 }); | |
2118 }); | |
2119 | |
2120 it('throws an error if only one endpoint is supplied', function(done) { | |
2121 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
2122 state.deploy('cs:wordpress', function() { | |
2123 var data = { | |
2124 RequestId: 42, | |
2125 Type: 'Client', | |
2126 Request: 'AddRelation', | |
2127 Params: { | |
2128 Endpoints: ['wordpress:db'] | |
2129 } | |
2130 }; | |
2131 client.onmessage = function(received) { | |
2132 var recData = Y.JSON.parse(received.data); | |
2133 assert.equal(recData.RequestId, data.RequestId); | |
2134 assert.equal(recData.Error, | |
2135 'Two string endpoint names required to establish a relation'); | |
2136 done(); | |
2137 }; | |
2138 client.open(); | |
2139 client.send(Y.JSON.stringify(data)); | |
2140 }); | |
2141 }); | |
2142 | |
2143 it('throws an error if endpoints are not relatable', function(done) { | |
2144 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
2145 state.deploy('cs:wordpress', function() { | |
2146 var data = { | |
2147 RequestId: 42, | |
2148 Type: 'Client', | |
2149 Request: 'AddRelation', | |
2150 Params: { | |
2151 Endpoints: ['wordpress:db', 'mysql:foo'] | |
2152 } | |
2153 }; | |
2154 client.onmessage = function(received) { | |
2155 var recData = Y.JSON.parse(received.data); | |
2156 assert.equal(recData.RequestId, data.RequestId); | |
2157 assert.equal(recData.Error, 'Charm not loaded.'); | |
2158 done(); | |
2159 }; | |
2160 client.open(); | |
2161 client.send(Y.JSON.stringify(data)); | |
2162 }); | |
2163 }); | |
2164 | |
2165 it('can remove a relation', function(done) { | |
2166 // We begin logged in. See utils.makeFakeBackendWithCharmStore. | |
2167 var relation = ['wordpress:db', 'mysql:db']; | |
2168 state.deploy('cs:wordpress', function() { | |
2169 state.deploy('cs:mysql', function() { | |
2170 state.addRelation(relation[0], relation[1]); | |
2171 var data = { | |
2172 RequestId: 42, | |
2173 Type: 'Client', | |
2174 Request: 'DestroyRelation', | |
2175 Params: { | |
2176 Endpoints: relation | |
2177 } | |
2178 }; | |
2179 client.onmessage = function(received) { | |
2180 var recData = Y.JSON.parse(received.data); | |
2181 assert.equal(recData.RequestId, data.RequestId); | |
2182 assert.equal(recData.Error, undefined); | |
2183 done(); | |
2184 }; | |
2185 client.open(); | |
2186 client.send(Y.JSON.stringify(data)); | |
2187 }); | |
2188 }); | |
2189 }); | |
2190 | |
2191 it('can remove a relation(integration)', function(done) { | |
2192 env.connect(); | |
2193 env.deploy('cs:wordpress', null, null, null, 1, function() { | |
2194 env.deploy('cs:mysql', null, null, null, 1, function() { | |
2195 var endpointA = ['wordpress', {name: 'db', role: 'client'}], | |
2196 endpointB = ['mysql', {name: 'db', role: 'server'}]; | |
2197 env.add_relation(endpointA, endpointB, function() { | |
2198 env.remove_relation(endpointA, endpointB, function(recData) { | |
2199 assert.equal(recData.err, undefined); | |
2200 assert.equal(recData.endpoint_a, 'wordpress:db'); | |
2201 assert.equal(recData.endpoint_b, 'mysql:db'); | |
2202 done(); | |
2203 }); | |
2204 }); | |
2205 }); | |
2206 }); | |
2207 }); | |
2208 | |
2209 }); | |
2210 | |
2211 })(); | 181 })(); |
OLD | NEW |