Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1533)

Side by Side Diff: test/test_sandbox.js

Issue 10746043: Split test_sandbox.js into pieces.
Patch Set: Split test_sandbox.js into pieces. Created 11 years, 9 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « test/index.html ('k') | test/test_sandbox_go.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 })();
OLDNEW
« no previous file with comments | « test/index.html ('k') | test/test_sandbox_go.js » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b