Merge lp:~teknico/juju-gui/split-sandbox-test-file into lp:juju-gui/experimental

Proposed by Nicola Larosa
Status: Merged
Merged at revision: 778
Proposed branch: lp:~teknico/juju-gui/split-sandbox-test-file
Merge into: lp:juju-gui/experimental
Diff against target: 4136 lines (+2077/-2030)
4 files modified
test/index.html (+2/-0)
test/test_sandbox.js (+0/-2030)
test/test_sandbox_go.js (+728/-0)
test/test_sandbox_python.js (+1347/-0)
To merge this branch: bzr merge lp:~teknico/juju-gui/split-sandbox-test-file
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+172042@code.launchpad.net

Description of the change

Split test_sandbox.js into pieces.

The test_sandbox.js file is too big, 2200+ lines and 75 KByte.

This splits the Go and Python sandbox tests out of it into
test_sandbox_go.js and test_sandbox_python.js . Only the ClientConnection
tests are left there.

Huge diff unavoidably, but it only moves code around, no other changes,
I promise. :-)

https://codereview.appspot.com/10746043/

To post a comment you must log in.
Revision history for this message
Nicola Larosa (teknico) wrote :

Reviewers: mp+172042_code.launchpad.net,

Message:
Please take a look.

Description:
Split test_sandbox.js into pieces.

The test_sandbox.js file is too big, 2200+ lines and 75 KByte.

This splits the Go and Python sandbox tests out of it into
test_sandbox_go.js and test_sandbox_python.js . Only the
ClientConnection
tests are left there.

Huge diff unavoidably, but it only moves code around, no other changes,
I promise. :-)

https://code.launchpad.net/~teknico/juju-gui/split-sandbox-test-file/+merge/172042

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/10746043/

Affected files:
   A [revision details]
   M test/index.html
   M test/test_sandbox.js
   A test/test_sandbox_go.js
   A test/test_sandbox_python.js

Revision history for this message
Jeff Pihach (hatch) wrote :

LGTM Thanks for splitting these up

https://codereview.appspot.com/10746043/

Revision history for this message
Francesco Banconi (frankban) wrote :
778. By Nicola Larosa

Merge from trunk.

Revision history for this message
Nicola Larosa (teknico) wrote :

*** Submitted:

Split test_sandbox.js into pieces.

The test_sandbox.js file is too big, 2200+ lines and 75 KByte.

This splits the Go and Python sandbox tests out of it into
test_sandbox_go.js and test_sandbox_python.js . Only the
ClientConnection
tests are left there.

Huge diff unavoidably, but it only moves code around, no other changes,
I promise. :-)

R=jeff.pihach, frankban
CC=
https://codereview.appspot.com/10746043

https://codereview.appspot.com/10746043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'test/index.html'
2--- test/index.html 2013-06-26 16:28:56 +0000
3+++ test/index.html 2013-06-28 15:03:23 +0000
4@@ -103,6 +103,8 @@
5 <script src="test_resizing_textarea.js"></script>
6 <script src="test_routing.js"></script>
7 <script src="test_sandbox.js"></script>
8+ <script src="test_sandbox_go.js"></script>
9+ <script src="test_sandbox_python.js"></script>
10
11 <!-- FIXME: tests flicker, add container. -->
12 <script src="test_service_config_view.js"></script>
13
14=== modified file 'test/test_sandbox.js'
15--- test/test_sandbox.js 2013-06-27 11:30:59 +0000
16+++ test/test_sandbox.js 2013-06-28 15:03:23 +0000
17@@ -178,2034 +178,4 @@
18
19 });
20
21- describe('sandbox.PyJujuAPI', function() {
22- var requires = [
23- 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-python',
24- 'juju-models', 'promise'];
25- var Y, sandboxModule, ClientConnection, environmentsModule, state, juju,
26- client, env, utils, cleanups;
27-
28- before(function(done) {
29- Y = YUI(GlobalConfig).use(requires, function(Y) {
30- sandboxModule = Y.namespace('juju.environments.sandbox');
31- environmentsModule = Y.namespace('juju.environments');
32- utils = Y.namespace('juju-tests.utils');
33- // A global variable required for testing.
34- window.flags = {};
35- done();
36- });
37- });
38-
39- beforeEach(function() {
40- state = utils.makeFakeBackendWithCharmStore();
41- juju = new sandboxModule.PyJujuAPI({state: state});
42- client = new sandboxModule.ClientConnection({juju: juju});
43- env = new environmentsModule.PythonEnvironment({conn: client});
44- cleanups = [];
45- });
46-
47- afterEach(function() {
48- Y.each(cleanups, function(f) {f();});
49- env.destroy();
50- client.destroy();
51- juju.destroy();
52- state.destroy();
53- });
54-
55- after(function() {
56- delete window.flags;
57- });
58-
59- /**
60- Generates the services required for some tests. After the services have
61- been generated it will call the supplied callback.
62-
63- This interacts directly with the fakebackend bypassing the environment.
64- The test "can add additional units" tests this code directly so as long
65- as it passes you can consider this method valid.
66-
67- @method generateServices
68- @param {Function} callback The callback to call after the services have
69- been generated.
70- */
71- function generateServices(callback) {
72- state.deploy('cs:wordpress', function(service) {
73- var data = {
74- op: 'add_unit',
75- service_name: 'wordpress',
76- num_units: 2
77- };
78- state.nextChanges();
79- client.onmessage = function() {
80- client.onmessage = function(received) {
81- // After done generating the services
82- callback(received);
83- };
84- client.send(Y.JSON.stringify(data));
85- };
86- client.open();
87- });
88- }
89-
90- /**
91- Generates the two services required for relation removal tests. After the
92- services have been generated, a relation between them will be added and
93- then removed.
94-
95- This interacts directly with the fakebackend bypassing the environment.
96-
97- @method generateAndRelateServices
98- @param {Array} charms The URLs of two charms to be deployed.
99- @param {Array} relation Two endpoint strings to be related.
100- @param {Array} removeRelation Two enpoint strings identifying
101- a relation to be removed.
102- @param {Object} mock Object with the expected return values of
103- the relation removal operation.
104- @param {Function} done To be called to signal the test end.
105- @return {undefined} Side effects only.
106- */
107- function generateAndRelateServices(charms, relation,
108- removeRelation, mock, done) {
109- state.deploy(charms[0], function() {
110- state.deploy(charms[1], function() {
111- if (relation) {
112- state.addRelation(relation[0], relation[1]);
113- }
114- var data = {
115- op: 'remove_relation',
116- endpoint_a: removeRelation[0],
117- endpoint_b: removeRelation[1]
118- };
119- client.onmessage = function(received) {
120- var recData = Y.JSON.parse(received.data);
121- // Skip the defaultSeriesChange message.
122- if (recData.default_series === undefined) {
123- assert.equal(recData.result, mock.result);
124- assert.equal(recData.err, mock.err);
125- if (!recData.err) {
126- assert.equal(recData.endpoint_a, mock.endpoint_a);
127- assert.equal(recData.endpoint_b, mock.endpoint_b);
128- }
129- done();
130- }
131- };
132- client.open();
133- client.send(Y.JSON.stringify(data));
134- });
135- });
136- }
137-
138- /**
139- Same as generateServices but uses the environment integration methods.
140- Should be considered valid if "can add additional units (integration)"
141- test passes.
142-
143- @method generateIntegrationServices
144- @param {Function} callback The callback to call after the services have
145- been generated.
146- */
147- function generateIntegrationServices(callback) {
148- env.after('defaultSeriesChange', function() {
149- var localCb = function(result) {
150- env.add_unit('kumquat', 2, function(data) {
151- // After finished generating integrated services
152- callback(data);
153- });
154- };
155- env.deploy(
156- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
157- });
158- env.connect();
159- }
160-
161- /**
162- Generates the services and then exposes them for the un/expose tests.
163- After they have been exposed it calls the supplied callback.
164-
165- This interacts directly with the fakebackend bypassing the environment and
166- should be considered valid if "can expose a service" test passes.
167-
168- @method generateAndExposeService
169- @param {Function} callback The callback to call after the services have
170- been generated.
171- */
172- function generateAndExposeService(callback) {
173- state.deploy('cs:wordpress', function(data) {
174- var command = {
175- op: 'expose',
176- service_name: data.service.get('name')
177- };
178- state.nextChanges();
179- client.onmessage = function() {
180- client.onmessage = function(rec) {
181- callback(rec);
182- };
183- client.send(Y.JSON.stringify(command));
184- };
185- client.open();
186- }, { unitCount: 1 });
187- }
188-
189- /**
190- Same as generateAndExposeService but uses the environment integration
191- methods. Should be considered valid if "can expose a service
192- (integration)" test passes.
193-
194- @method generateAndExposeIntegrationService
195- @param {Function} callback The callback to call after the services have
196- been generated.
197- */
198- function generateAndExposeIntegrationService(callback) {
199- env.after('defaultSeriesChange', function() {
200- var localCb = function(result) {
201- env.expose(result.service_name, function(rec) {
202- callback(rec);
203- });
204- };
205- env.deploy(
206- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
207- });
208- env.connect();
209- }
210-
211- it('opens successfully.', function(done) {
212- var isAsync = false;
213- client.onmessage = function(message) {
214- assert.isTrue(isAsync);
215- assert.deepEqual(
216- Y.JSON.parse(message.data),
217- {
218- ready: true,
219- provider_type: 'demonstration',
220- default_series: 'precise'
221- });
222- done();
223- };
224- assert.isFalse(juju.connected);
225- assert.isUndefined(juju.get('client'));
226- client.open();
227- assert.isTrue(juju.connected);
228- assert.strictEqual(juju.get('client'), client);
229- isAsync = true;
230- });
231-
232- it('ignores "open" when already open to same client.', function() {
233- client.receive = function() {
234- assert.ok(false, 'The receive method should not be called.');
235- };
236- // Whitebox test: duplicate "open" state.
237- juju.connected = true;
238- juju.set('client', client);
239- // This is effectively a re-open.
240- client.open();
241- // The assert.ok above is the verification.
242- });
243-
244- it('refuses to open if already open to another client.', function() {
245- // This is a simple way to make sure that we don't leave multiple
246- // setInterval calls running. If for some reason we want more
247- // simultaneous clients, that's fine, though that will require
248- // reworking the delta code generally.
249- juju.connected = true;
250- juju.set('client', {receive: function() {
251- assert.ok(false, 'The receive method should not have been called.');
252- }});
253- assert.throws(
254- client.open.bind(client),
255- 'INVALID_STATE_ERR : Connection is open to another client.');
256- });
257-
258- it('closes successfully.', function(done) {
259- client.onmessage = function() {
260- client.close();
261- assert.isFalse(juju.connected);
262- assert.isUndefined(juju.get('client'));
263- done();
264- };
265- client.open();
266- });
267-
268- it('ignores "close" when already closed.', function() {
269- // This simply shows that we do not raise an error.
270- juju.close();
271- });
272-
273- it('can dispatch on received information.', function(done) {
274- var data = {op: 'testingTesting123', foo: 'bar'};
275- juju.performOp_testingTesting123 = function(received) {
276- assert.notStrictEqual(received, data);
277- assert.deepEqual(received, data);
278- done();
279- };
280- client.open();
281- client.send(Y.JSON.stringify(data));
282- });
283-
284- it('refuses to dispatch when closed.', function() {
285- assert.throws(
286- juju.receive.bind(juju, {}),
287- 'INVALID_STATE_ERR : Connection is closed.'
288- );
289- });
290-
291- it('can log in.', function(done) {
292- state.logout();
293- // See FakeBackend's authorizedUsers for these default authentication
294- // values.
295- var data = {
296- op: 'login',
297- user: 'admin',
298- password: 'password',
299- request_id: 42
300- };
301- client.onmessage = function(received) {
302- // First message is the provider type and default series. We ignore
303- // it, and prepare for the next one, which will be the reply to our
304- // login.
305- client.onmessage = function(received) {
306- data.result = true;
307- assert.deepEqual(Y.JSON.parse(received.data), data);
308- assert.isTrue(state.get('authenticated'));
309- done();
310- };
311- client.send(Y.JSON.stringify(data));
312- };
313- client.open();
314- });
315-
316- it('can log in (environment integration).', function(done) {
317- state.logout();
318- env.after('defaultSeriesChange', function() {
319- // See FakeBackend's authorizedUsers for these default values.
320- env.setCredentials({user: 'admin', password: 'password'});
321- env.after('login', function() {
322- assert.isTrue(env.userIsAuthenticated);
323- done();
324- });
325- env.login();
326- });
327- env.connect();
328- });
329-
330- it('can deploy.', function(done) {
331- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
332- var data = {
333- op: 'deploy',
334- charm_url: 'cs:wordpress',
335- service_name: 'kumquat',
336- config_raw: 'funny: business',
337- num_units: 2,
338- request_id: 42
339- };
340- client.onmessage = function(received) {
341- // First message is the provider type and default series. We ignore
342- // it, and prepare for the next one, which will be the reply to our
343- // deployment.
344- client.onmessage = function(received) {
345- var parsed = Y.JSON.parse(received.data);
346- assert.isUndefined(parsed.err);
347- assert.deepEqual(parsed, data);
348- assert.isObject(
349- state.db.charms.getById('cs:precise/wordpress-10'));
350- var service = state.db.services.getById('kumquat');
351- assert.isObject(service);
352- assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
353- assert.deepEqual(service.get('config'), {funny: 'business'});
354- var units = state.db.units.get_units_for_service(service);
355- assert.lengthOf(units, 2);
356- done();
357- };
358- client.send(Y.JSON.stringify(data));
359- };
360- client.open();
361- });
362-
363- it('can deploy (environment integration).', function(done) {
364- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
365- env.after('defaultSeriesChange', function() {
366- var callback = function(result) {
367- assert.isUndefined(result.err);
368- assert.equal(result.charm_url, 'cs:wordpress');
369- var service = state.db.services.getById('kumquat');
370- assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
371- assert.deepEqual(service.get('config'), {llama: 'pajama'});
372- done();
373- };
374- env.deploy(
375- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback);
376- });
377- env.connect();
378- });
379-
380- it('can communicate errors after attempting to deploy', function(done) {
381- // Create a service with the name "wordpress".
382- // The charm store is synchronous in tests, so we don't need a real
383- // callback.
384- state.deploy('cs:wordpress', function() {});
385- env.after('defaultSeriesChange', function() {
386- var callback = function(result) {
387- assert.equal(
388- result.err, 'A service with this name already exists.');
389- done();
390- };
391- env.deploy(
392- 'cs:wordpress', undefined, undefined, undefined, 1, callback);
393- });
394- env.connect();
395- });
396-
397- it('can send a delta stream of changes.', function(done) {
398- // Create a service with the name "wordpress".
399- // The charm store is synchronous in tests, so we don't need a real
400- // callback.
401- state.deploy('cs:wordpress', function() {});
402- client.onmessage = function(received) {
403- // First message is the provider type and default series. We ignore
404- // it, and prepare for the next one, which will handle the delta
405- // stream.
406- client.onmessage = function(received) {
407- var parsed = Y.JSON.parse(received.data);
408- assert.equal(parsed.op, 'delta');
409- var deltas = parsed.result;
410- assert.lengthOf(deltas, 3);
411- assert.equal(deltas[0][0], 'service');
412- assert.equal(deltas[0][1], 'change');
413- assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10');
414- assert.equal(deltas[1][0], 'machine');
415- assert.equal(deltas[1][1], 'change');
416- assert.equal(deltas[2][0], 'unit');
417- assert.equal(deltas[2][1], 'change');
418- done();
419- };
420- juju.sendDelta();
421- };
422- client.open();
423- });
424-
425- it('does not send a delta if there are no changes.', function(done) {
426- client.onmessage = function(received) {
427- // First message is the provider type and default series. We ignore
428- // it, and prepare for the next one, which will handle the delta
429- // stream.
430- client.receiveNow = function(response) {
431- assert.ok(false, 'This method should not have been called.');
432- };
433- juju.sendDelta();
434- done();
435- };
436- client.open();
437- });
438-
439- it('can send a delta stream (integration).', function(done) {
440- // Create a service with the name "wordpress".
441- // The charm store is synchronous in tests, so we don't need a real
442- // callback.
443- state.deploy('cs:wordpress', function() {}, {unitCount: 2});
444- var db = new Y.juju.models.Database();
445- db.on('update', function() {
446- // We want to verify that the GUI database is equivalent to the state
447- // database.
448- assert.equal(db.services.size(), 1);
449- assert.equal(db.units.size(), 2);
450- assert.equal(db.machines.size(), 2);
451- var stateService = state.db.services.item(0);
452- var guiService = db.services.item(0);
453- Y.each(
454- ['charm', 'config', 'constraints', 'exposed',
455- 'id', 'name', 'subordinate'],
456- function(attrName) {
457- assert.deepEqual(
458- guiService.get(attrName), stateService.get(attrName));
459- }
460- );
461- state.db.units.each(function(stateUnit) {
462- var guiUnit = db.units.getById(stateUnit.id);
463- Y.each(
464- ['agent_state', 'machine', 'number', 'service'],
465- function(attrName) {
466- assert.deepEqual(guiUnit[attrName], stateUnit[attrName]);
467- }
468- );
469- });
470- state.db.machines.each(function(stateMachine) {
471- var guiMachine = db.machines.getById(stateMachine.id);
472- Y.each(
473- ['agent_state', 'public_address', 'machine_id'],
474- function(attrName) {
475- assert.deepEqual(guiMachine[attrName], stateMachine[attrName]);
476- }
477- );
478- });
479- done();
480- });
481- env.on('delta', db.onDelta, db);
482- env.after('defaultSeriesChange', function() {juju.sendDelta();});
483- env.connect();
484- });
485-
486- it('sends delta streams periodically after opening.', function(done) {
487- client.onmessage = function(received) {
488- // First message is the provider type and default series. We ignore
489- // it, and prepare for the next one, which will handle the delta
490- // stream.
491- var isAsync = false;
492- client.onmessage = function(received) {
493- assert.isTrue(isAsync);
494- var parsed = Y.JSON.parse(received.data);
495- assert.equal(parsed.op, 'delta');
496- var deltas = parsed.result;
497- assert.lengthOf(deltas, 3);
498- assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10');
499- done();
500- };
501- // Create a service with the name "wordpress".
502- // The charm store is synchronous in tests, so we don't need a real
503- // callback.
504- state.deploy('cs:wordpress', function() {});
505- isAsync = true;
506- };
507- juju.set('deltaInterval', 4);
508- client.open();
509- });
510-
511- it('stops sending delta streams after closing.', function(done) {
512- var sysSetInterval = window.setInterval;
513- var sysClearInterval = window.clearInterval;
514- cleanups.push(function() {
515- window.setInterval = sysSetInterval;
516- window.clearInterval = sysClearInterval;
517- });
518- window.setInterval = function(f, interval) {
519- assert.isFunction(f);
520- assert.equal(interval, 4);
521- return 42;
522- };
523- window.clearInterval = function(token) {
524- assert.equal(token, 42);
525- done();
526- };
527- client.onmessage = function(received) {
528- // First message is the provider type and default series. We can
529- // close now.
530- client.close();
531- };
532- juju.set('deltaInterval', 4);
533- client.open();
534- });
535-
536- it('can add additional units', function(done) {
537- function testForAddedUnits(received) {
538- var service = state.db.services.getById('wordpress'),
539- units = state.db.units.get_units_for_service(service),
540- data = Y.JSON.parse(received.data),
541- mock = {
542- num_units: 2,
543- service_name: 'wordpress',
544- op: 'add_unit',
545- result: ['wordpress/1', 'wordpress/2']
546- };
547- // Do we have enough total units?
548- assert.lengthOf(units, 3);
549- // Does the response object contain the proper data
550- assert.deepEqual(data, mock);
551- // Error is undefined
552- assert.isUndefined(data.err);
553- done();
554- }
555- // Generate the default services and add units
556- generateServices(testForAddedUnits);
557- });
558-
559- it('throws an error when adding units to an invalid service',
560- function(done) {
561- state.deploy('cs:wordpress', function(service) {
562- var data = {
563- op: 'add_unit',
564- service_name: 'noservice',
565- num_units: 2
566- };
567- //Clear out the delta stream
568- state.nextChanges();
569- client.onmessage = function() {
570- client.onmessage = function(received) {
571- var data = Y.JSON.parse(received.data);
572-
573- // If there is no error data.err will be undefined
574- assert.equal(true, !!data.err);
575- done();
576- };
577- client.send(Y.JSON.stringify(data));
578- };
579- client.open();
580- });
581- }
582- );
583-
584- it('can add additional units (integration)', function(done) {
585- function testForAddedUnits(data) {
586- var service = state.db.services.getById('kumquat'),
587- units = state.db.units.get_units_for_service(service);
588- assert.lengthOf(units, 3);
589- done();
590- }
591- generateIntegrationServices(testForAddedUnits);
592- });
593-
594- it('can remove units', function(done) {
595- function removeUnits() {
596- var data = {
597- op: 'remove_units',
598- unit_names: ['wordpress/0', 'wordpress/1']
599- };
600- client.onmessage = function(rec) {
601- var data = Y.JSON.parse(rec.data),
602- mock = {
603- op: 'remove_units',
604- result: true,
605- unit_names: ['wordpress/0', 'wordpress/1']
606- };
607- // No errors
608- assert.equal(data.result, true);
609- // Returned data object contains all information
610- assert.deepEqual(data, mock);
611- done();
612- };
613- client.send(Y.JSON.stringify(data));
614- }
615- // Generate the services base data and then execute the test
616- generateServices(removeUnits);
617- });
618-
619- it('can remove units (integration)', function(done) {
620- function removeUnits() {
621- var unitNames = ['kumquat/1', 'kumquat/2'];
622- env.remove_units(unitNames, function(data) {
623- assert.equal(data.result, true);
624- assert.deepEqual(data.unit_names, unitNames);
625- done();
626- });
627- }
628- // Generate the services via the integration method then execute the test
629- generateIntegrationServices(removeUnits);
630- });
631-
632- it('allows attempting to remove units from an invalid service',
633- function(done) {
634- function removeUnit() {
635- var data = {
636- op: 'remove_units',
637- unit_names: ['bar/2']
638- };
639- client.onmessage = function(rec) {
640- var data = Y.JSON.parse(rec.data);
641- assert.equal(data.result, true);
642- done();
643- };
644- client.send(Y.JSON.stringify(data));
645- }
646- // Generate the services base data then execute the test.
647- generateServices(removeUnit);
648- }
649- );
650-
651- it('throws an error if unit is a subordinate', function(done) {
652- function removeUnits() {
653- var data = {
654- op: 'remove_units',
655- unit_names: ['wordpress/1']
656- };
657- client.onmessage = function(rec) {
658- var data = Y.JSON.parse(rec.data);
659- assert.equal(Y.Lang.isArray(data.err), true);
660- assert.equal(data.err.length, 1);
661- done();
662- };
663- state.db.services.getById('wordpress').set('is_subordinate', true);
664- client.send(Y.JSON.stringify(data));
665- }
666- // Generate the services base data then execute the test.
667- generateServices(removeUnits);
668- });
669-
670- it('can get a service', function(done) {
671- generateServices(function(data) {
672- // Post deploy of wordpress so we should be able to
673- // pull its data.
674- var op = {
675- op: 'get_service',
676- service_name: 'wordpress',
677- request_id: 99
678- };
679- client.onmessage = function(received) {
680- var parsed = Y.JSON.parse(received.data);
681- var service = parsed.result;
682- assert.equal(service.name, 'wordpress');
683- // Error should be undefined.
684- done(received.error);
685- };
686- client.send(Y.JSON.stringify(op));
687- });
688- });
689-
690- it('can destroy a service', function(done) {
691- generateServices(function(data) {
692- // Post deploy of wordpress so we should be able to
693- // destroy it.
694- var op = {
695- op: 'destroy_service',
696- service_name: 'wordpress',
697- request_id: 99
698- };
699- client.onmessage = function(received) {
700- var parsed = Y.JSON.parse(received.data);
701- assert.equal(parsed.result, 'wordpress');
702- // Error should be undefined.
703- done(received.error);
704- };
705- client.send(Y.JSON.stringify(op));
706- });
707- });
708-
709- it('can destroy a service (integration)', function(done) {
710- function destroyService(rec) {
711- function localCb(rec2) {
712- assert.equal(rec2.result, 'kumquat');
713- var service = state.db.services.getById('kumquat');
714- assert.isNull(service);
715- done();
716- }
717- var result = env.destroy_service(rec.service_name, localCb);
718- }
719- generateAndExposeIntegrationService(destroyService);
720- });
721-
722- it('can get a charm', function(done) {
723- generateServices(function(data) {
724- // Post deploy of wordpress we should be able to
725- // pull its data.
726- var op = {
727- op: 'get_charm',
728- charm_url: 'cs:wordpress',
729- request_id: 99
730- };
731- client.onmessage = function(received) {
732- var parsed = Y.JSON.parse(received.data);
733- var charm = parsed.result;
734- assert.equal(charm.name, 'wordpress');
735- // Error should be undefined.
736- done(received.error);
737- };
738- client.send(Y.JSON.stringify(op));
739- });
740- });
741-
742- it('can set service config', function(done) {
743- generateServices(function(data) {
744- // Post deploy of wordpress we should be able to
745- // pull its data.
746- var op = {
747- op: 'set_config',
748- service_name: 'wordpress',
749- config: {'blog-title': 'Inimical'},
750- request_id: 99
751- };
752- client.onmessage = function(received) {
753- var parsed = Y.JSON.parse(received.data);
754- assert.deepEqual(parsed.result, {'blog-title': 'Inimical'});
755- var service = state.db.services.getById('wordpress');
756- assert.equal(service.get('config')['blog-title'], 'Inimical');
757- // Error should be undefined.
758- done(parsed.error);
759- };
760- client.send(Y.JSON.stringify(op));
761- });
762- });
763-
764- it('can set service constraints', function(done) {
765- generateServices(function(data) {
766- // Post deploy of wordpress we should be able to
767- // pull its data.
768- var op = {
769- op: 'set_constraints',
770- service_name: 'wordpress',
771- constraints: ['cpu=2', 'mem=128'],
772- request_id: 99
773- };
774- client.onmessage = function(received) {
775- var service = state.db.services.getById('wordpress');
776- var constraints = service.get('constraints');
777- assert.equal(constraints.cpu, '2');
778- assert.equal(constraints.mem, '128');
779- // Error should be undefined.
780- done(received.error);
781- };
782- client.send(Y.JSON.stringify(op));
783- });
784- });
785-
786- it('can expose a service', function(done) {
787- function checkExposedService(rec) {
788- var data = Y.JSON.parse(rec.data),
789- mock = {
790- op: 'expose',
791- result: true,
792- service_name: 'wordpress'
793- };
794- var service = state.db.services.getById(mock.service_name);
795- assert.equal(service.get('exposed'), true);
796- assert.equal(data.result, true);
797- assert.deepEqual(data, mock);
798- done();
799- }
800- generateAndExposeService(checkExposedService);
801- });
802-
803- it('can expose a service (integration)', function(done) {
804- function checkExposedService(rec) {
805- var service = state.db.services.getById('kumquat');
806- assert.equal(service.get('exposed'), true);
807- assert.equal(rec.result, true);
808- done();
809- }
810- generateAndExposeIntegrationService(checkExposedService);
811- });
812-
813- it('fails silently when exposing an exposed service', function(done) {
814- function checkExposedService(rec) {
815- var data = Y.JSON.parse(rec.data),
816- service = state.db.services.getById(data.service_name),
817- command = {
818- op: 'expose',
819- service_name: data.service_name
820- };
821- state.nextChanges();
822- client.onmessage = function(rec) {
823- assert.equal(data.err, undefined);
824- assert.equal(service.get('exposed'), true);
825- assert.equal(data.result, true);
826- done();
827- };
828- client.send(Y.JSON.stringify(command));
829- }
830- generateAndExposeService(checkExposedService);
831- });
832-
833- it('fails with error when exposing an invalid service name',
834- function(done) {
835- state.deploy('cs:wordpress', function(data) {
836- var command = {
837- op: 'expose',
838- service_name: 'foobar'
839- };
840- state.nextChanges();
841- client.onmessage = function() {
842- client.onmessage = function(rec) {
843- var data = Y.JSON.parse(rec.data);
844- assert.equal(data.result, false);
845- assert.equal(data.err,
846- '"foobar" is an invalid service name.');
847- done();
848- };
849- client.send(Y.JSON.stringify(command));
850- };
851- client.open();
852- }, { unitCount: 1 });
853- }
854- );
855-
856- it('can unexpose a service', function(done) {
857- function unexposeService(rec) {
858- var data = Y.JSON.parse(rec.data),
859- command = {
860- op: 'unexpose',
861- service_name: data.service_name
862- };
863- state.nextChanges();
864- client.onmessage = function(rec) {
865- var data = Y.JSON.parse(rec.data),
866- service = state.db.services.getById(data.service_name),
867- mock = {
868- op: 'unexpose',
869- result: true,
870- service_name: 'wordpress'
871- };
872- assert.equal(service.get('exposed'), false);
873- assert.deepEqual(data, mock);
874- done();
875- };
876- client.send(Y.JSON.stringify(command));
877- }
878- generateAndExposeService(unexposeService);
879- });
880-
881- it('can unexpose a service (integration)', function(done) {
882- function unexposeService(rec) {
883- function localCb(rec) {
884- var service = state.db.services.getById('kumquat');
885- assert.equal(service.get('exposed'), false);
886- assert.equal(rec.result, true);
887- done();
888- }
889- env.unexpose(rec.service_name, localCb);
890- }
891- generateAndExposeIntegrationService(unexposeService);
892- });
893-
894- it('fails silently when unexposing a not exposed service',
895- function(done) {
896- state.deploy('cs:wordpress', function(data) {
897- var command = {
898- op: 'unexpose',
899- service_name: data.service.get('name')
900- };
901- state.nextChanges();
902- client.onmessage = function() {
903- client.onmessage = function(rec) {
904- var data = Y.JSON.parse(rec.data),
905- service = state.db.services.getById(data.service_name);
906- assert.equal(service.get('exposed'), false);
907- assert.equal(data.result, true);
908- assert.equal(data.err, undefined);
909- done();
910- };
911- client.send(Y.JSON.stringify(command));
912- };
913- client.open();
914- }, { unitCount: 1 });
915- }
916- );
917-
918- it('fails with error when unexposing an invalid service name',
919- function(done) {
920- function unexposeService(rec) {
921- var data = Y.JSON.parse(rec.data),
922- command = {
923- op: 'unexpose',
924- service_name: 'foobar'
925- };
926- state.nextChanges();
927- client.onmessage = function(rec) {
928- var data = Y.JSON.parse(rec.data);
929- assert.equal(data.result, false);
930- assert.equal(data.err, '"foobar" is an invalid service name.');
931- done();
932- };
933- client.send(Y.JSON.stringify(command));
934- }
935- generateAndExposeService(unexposeService);
936- }
937- );
938-
939- it('can add a relation', function(done) {
940- function localCb() {
941- state.deploy('cs:mysql', function(service) {
942- var data = {
943- op: 'add_relation',
944- endpoint_a: 'wordpress:db',
945- endpoint_b: 'mysql:db'
946- };
947- client.onmessage = function(rec) {
948- var data = Y.JSON.parse(rec.data),
949- mock = {
950- endpoint_a: 'wordpress:db',
951- endpoint_b: 'mysql:db',
952- op: 'add_relation',
953- result: {
954- id: 'relation-0',
955- 'interface': 'mysql',
956- scope: 'global',
957- endpoints: [
958- {wordpress: {name: 'db'}},
959- {mysql: {name: 'db'}}
960- ]
961- }
962- };
963-
964- assert.equal(data.err, undefined);
965- assert.equal(typeof data.result, 'object');
966- assert.deepEqual(data, mock);
967- done();
968- };
969- client.send(Y.JSON.stringify(data));
970- });
971- }
972- generateServices(localCb);
973- });
974-
975- it('can add a relation (integration)', function(done) {
976- function addRelation() {
977- function localCb(rec) {
978- var mock = {
979- endpoint_a: 'kumquat:db',
980- endpoint_b: 'mysql:db',
981- op: 'add_relation',
982- request_id: rec.request_id,
983- result: {
984- id: 'relation-0',
985- 'interface': 'mysql',
986- scope: 'global',
987- request_id: rec.request_id,
988- endpoints: [
989- {kumquat: {name: 'db'}},
990- {mysql: {name: 'db'}}
991- ]
992- }
993- };
994-
995- assert.equal(rec.err, undefined);
996- assert.equal(typeof rec.result, 'object');
997- assert.deepEqual(rec.details[0], mock);
998- done();
999- }
1000- var endpointA = [
1001- 'kumquat',
1002- { name: 'db',
1003- role: 'client' }
1004- ];
1005- var endpointB = [
1006- 'mysql',
1007- { name: 'db',
1008- role: 'server' }
1009- ];
1010- env.add_relation(endpointA, endpointB, localCb);
1011- }
1012- generateIntegrationServices(function() {
1013- env.deploy('cs:mysql', undefined, undefined, undefined, 1, addRelation);
1014- });
1015- });
1016-
1017- it('is able to add a relation with a subordinate service', function(done) {
1018- function localCb() {
1019- state.deploy('cs:puppet', function(service) {
1020- var data = {
1021- op: 'add_relation',
1022- endpoint_a: 'wordpress:juju-info',
1023- endpoint_b: 'puppet:juju-info'
1024- };
1025-
1026- client.onmessage = function(rec) {
1027- var data = Y.JSON.parse(rec.data),
1028- mock = {
1029- endpoint_a: 'wordpress:juju-info',
1030- endpoint_b: 'puppet:juju-info',
1031- op: 'add_relation',
1032- result: {
1033- id: 'relation-0',
1034- 'interface': 'juju-info',
1035- scope: 'container',
1036- endpoints: [
1037- {puppet: {name: 'juju-info'}},
1038- {wordpress: {name: 'juju-info'}}
1039- ]
1040- }
1041- };
1042- assert.equal(data.err, undefined);
1043- assert.equal(typeof data.result, 'object');
1044- assert.deepEqual(data, mock);
1045- done();
1046- };
1047- client.send(Y.JSON.stringify(data));
1048- });
1049- }
1050- generateServices(localCb);
1051- });
1052-
1053- it('throws an error if only one endpoint is supplied', function(done) {
1054- function localCb() {
1055- var data = {
1056- op: 'add_relation',
1057- endpoint_a: 'wordpress:db'
1058- };
1059- state.nextChanges();
1060- client.onmessage = function(rec) {
1061- var data = Y.JSON.parse(rec.data);
1062- assert(data.err, 'Two endpoints required to set up relation.');
1063- done();
1064- };
1065- client.send(Y.JSON.stringify(data));
1066- }
1067- generateServices(localCb);
1068- });
1069-
1070- it('throws an error if endpoints are not relatable', function(done) {
1071- function localCb() {
1072- var data = {
1073- op: 'add_relation',
1074- endpoint_a: 'wordpress:db',
1075- endpoint_b: 'mysql:foo'
1076- };
1077- state.nextChanges();
1078- client.onmessage = function(rec) {
1079- var data = Y.JSON.parse(rec.data);
1080- assert(data.err, 'No matching interfaces.');
1081- done();
1082- };
1083- client.send(Y.JSON.stringify(data));
1084- }
1085- generateServices(localCb);
1086- });
1087-
1088- it('can remove a relation', function(done) {
1089- generateAndRelateServices(
1090- ['cs:wordpress', 'cs:mysql'],
1091- ['wordpress:db', 'mysql:db'],
1092- ['wordpress:db', 'mysql:db'],
1093- {result: true, endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
1094- done);
1095- });
1096-
1097- it('can remove a relation (integration)', function(done) {
1098- var endpoints = [
1099- ['kumquat',
1100- { name: 'db',
1101- role: 'client' }],
1102- ['mysql',
1103- { name: 'db',
1104- role: 'server' }]
1105- ];
1106- env.after('defaultSeriesChange', function() {
1107- function localCb(result) {
1108- var mock = {
1109- endpoint_a: 'kumquat:db',
1110- endpoint_b: 'mysql:db',
1111- op: 'remove_relation',
1112- request_id: 4,
1113- result: true
1114- };
1115- assert.deepEqual(result.details[0], mock);
1116- done();
1117- }
1118- env.deploy(
1119- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, function() {
1120- env.deploy('cs:mysql', null, null, null, 1, function() {
1121- env.add_relation(endpoints[0], endpoints[1], function() {
1122- env.remove_relation(endpoints[0], endpoints[1], localCb);
1123- });
1124- });
1125- }
1126- );
1127- });
1128- env.connect();
1129- });
1130-
1131- it('throws an error if the charms do not exist', function(done) {
1132- generateAndRelateServices(
1133- ['cs:wordpress', 'cs:mysql'],
1134- ['wordpress:db', 'mysql:db'],
1135- ['no_such', 'charms'],
1136- {err: 'Charm not loaded.',
1137- endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
1138- done);
1139- });
1140-
1141- it('throws an error if the relationship does not exist', function(done) {
1142- generateAndRelateServices(
1143- ['cs:wordpress', 'cs:mysql'],
1144- null,
1145- ['wordpress:db', 'mysql:db'],
1146- {err: 'Relationship does not exist',
1147- endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
1148- done);
1149- });
1150-
1151- describe('Sandbox Annotations', function() {
1152-
1153- it('should handle service annotation updates', function(done) {
1154- generateServices(function(data) {
1155- // Post deploy of wordpress we should be able to
1156- // pull its data.
1157- var op = {
1158- op: 'update_annotations',
1159- entity: 'wordpress',
1160- data: {'foo': 'bar'},
1161- request_id: 99
1162- };
1163- client.onmessage = function(received) {
1164- var service = state.db.services.getById('wordpress');
1165- var annotations = service.get('annotations');
1166- assert.equal(annotations.foo, 'bar');
1167- // Validate that annotations appear in the delta stream.
1168- client.onmessage = function(delta) {
1169- delta = Y.JSON.parse(delta.data);
1170- assert.equal(delta.op, 'delta');
1171- var serviceChange = Y.Array.find(delta.result, function(change) {
1172- return change[0] === 'service';
1173- });
1174- assert.equal(serviceChange[0], 'service');
1175- assert.equal(serviceChange[1], 'change');
1176- assert.deepEqual(serviceChange[2].annotations, {'foo': 'bar'});
1177- // Error should be undefined.
1178- done(received.error);
1179- };
1180- juju.sendDelta();
1181- };
1182- client.open();
1183- client.send(Y.JSON.stringify(op));
1184- });
1185- });
1186-
1187- it('should handle environment annotation updates', function(done) {
1188- generateServices(function(data) {
1189- // We only deploy a service here to reuse the env connect/setup
1190- // code.
1191- // Post deploy of wordpress we should be able to
1192- // pull env data.
1193- client.onmessage = function(received) {
1194- var env = state.db.environment;
1195- var annotations = env.get('annotations');
1196- assert.equal(annotations.foo, 'bar');
1197- // Validate that annotations appear in the delta stream.
1198- client.onmessage = function(delta) {
1199- delta = Y.JSON.parse(delta.data);
1200- assert.equal(delta.op, 'delta');
1201- var envChange = Y.Array.find(delta.result, function(change) {
1202- return change[0] === 'annotations';
1203- });
1204- assert.equal(envChange[1], 'change');
1205- assert.deepEqual(envChange[2], {'foo': 'bar'});
1206- done();
1207- };
1208- juju.sendDelta();
1209- };
1210- client.open();
1211- client.send(Y.JSON.stringify({
1212- op: 'update_annotations',
1213- entity: 'env',
1214- data: {'foo': 'bar'},
1215- request_id: 99
1216- }));
1217- });
1218- });
1219-
1220- it('should handle unit annotation updates', function(done) {
1221- generateServices(function(data) {
1222- // Post deploy of wordpress we should be able to
1223- // pull its data.
1224- var op = {
1225- op: 'update_annotations',
1226- entity: 'wordpress/0',
1227- data: {'foo': 'bar'},
1228- request_id: 99
1229- };
1230- client.onmessage = function(received) {
1231- var unit = state.db.units.getById('wordpress/0');
1232- var annotations = unit.annotations;
1233- assert.equal(annotations.foo, 'bar');
1234- // Error should be undefined.
1235- done(received.error);
1236- };
1237- client.open();
1238- client.send(Y.JSON.stringify(op));
1239- });
1240- });
1241-
1242- });
1243-
1244- it('should allow unit resolved to be called', function(done) {
1245- generateServices(function(data) {
1246- // Post deploy of wordpress we should be able to
1247- // pull its data.
1248- var op = {
1249- op: 'resolved',
1250- unit_name: 'wordpress/0',
1251- request_id: 99
1252- };
1253- client.onmessage = function(received) {
1254- var parsed = Y.JSON.parse(received.data);
1255- assert.equal(parsed.result, true);
1256- done(parsed.error);
1257- };
1258- client.open();
1259- client.send(Y.JSON.stringify(op));
1260- });
1261- });
1262-
1263- /**
1264- * Utility method to turn _some_ callback
1265- * styled async methods into Promises.
1266- * It does this by supplying a simple
1267- * adaptor that can handle {error:...}
1268- * and {result: ... } returns.
1269- *
1270- * This callback is appended to any calling arguments
1271- *
1272- * @method promise
1273- * @param {Object} context Calling context.
1274- * @param {String} methodName name of method on context to invoke.
1275- * @param {Arguments} arguments Additional arguments passed
1276- * to resolved method.
1277- * @return {Promise} a Y.Promise object.
1278- */
1279- function promise(context, methodName) {
1280- var slice = Array.prototype.slice;
1281- var args = slice.call(arguments, 2);
1282- var method = context[methodName];
1283-
1284- return Y.Promise(function(resolve, reject) {
1285- var resultHandler = function(result) {
1286- if (result.err || result.error) {
1287- reject(result.err || result.error);
1288- } else {
1289- resolve(result);
1290- }
1291- };
1292-
1293- args.push(resultHandler);
1294- var result = method.apply(context, args);
1295- if (result !== undefined) {
1296- // The method returned right away.
1297- return resultHandler(result);
1298- }
1299- });
1300- }
1301-
1302- it('should support export', function(done) {
1303- client.open();
1304- promise(state, 'deploy', 'cs:wordpress')
1305- .then(promise(state, 'deploy', 'cs:mysql'))
1306- .then(promise(state, 'addRelation', 'wordpress:db', 'mysql:db'))
1307- .then(function() {
1308- client.onmessage = function(result) {
1309- var data = Y.JSON.parse(result.data).result;
1310- assert.equal(data.services[0].name, 'wordpress');
1311- done();
1312- };
1313- client.send(Y.JSON.stringify({op: 'exportEnvironment'}));
1314- });
1315- });
1316-
1317- it('should support import', function(done) {
1318- var fixture = utils.loadFixture('data/sample-fakebackend.json', false);
1319-
1320- client.onmessage = function() {
1321- client.onmessage = function(result) {
1322- var data = Y.JSON.parse(result.data).result;
1323- assert.isTrue(data);
1324-
1325- // Verify that we can now find an expected entry
1326- // in the database.
1327- assert.isNotNull(state.db.services.getById('wordpress'));
1328-
1329- var changes = state.nextChanges();
1330- // Validate the delta includes imported services.
1331- assert.include(changes.services, 'wordpress');
1332- assert.include(changes.services, 'mysql');
1333- // validate relation was added/updated.
1334- assert.include(changes.relations, 'relation-0');
1335- done();
1336- };
1337- client.send(Y.JSON.stringify({op: 'importEnvironment',
1338- envData: fixture}));
1339- };
1340- client.open();
1341- });
1342-
1343- });
1344-
1345-
1346- describe('sandbox.GoJujuAPI', function() {
1347- var requires = [
1348- 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-go',
1349- 'juju-models', 'promise'];
1350- var Y, sandboxModule, ClientConnection, environmentsModule, state, juju,
1351- client, env, utils;
1352-
1353- before(function(done) {
1354- Y = YUI(GlobalConfig).use(requires, function(Y) {
1355- sandboxModule = Y.namespace('juju.environments.sandbox');
1356- environmentsModule = Y.namespace('juju.environments');
1357- utils = Y.namespace('juju-tests.utils');
1358- // A global variable required for testing.
1359- window.flags = {};
1360- done();
1361- });
1362- });
1363-
1364- beforeEach(function() {
1365- state = utils.makeFakeBackendWithCharmStore();
1366- juju = new sandboxModule.GoJujuAPI({state: state});
1367- client = new sandboxModule.ClientConnection({juju: juju});
1368- env = new environmentsModule.GoEnvironment({conn: client});
1369- });
1370-
1371- afterEach(function() {
1372- env.destroy();
1373- client.destroy();
1374- juju.destroy();
1375- state.destroy();
1376- });
1377-
1378- after(function() {
1379- delete window.flags;
1380- });
1381-
1382- it('opens successfully.', function() {
1383- assert.isFalse(juju.connected);
1384- assert.isUndefined(juju.get('client'));
1385- client.open();
1386- assert.isTrue(juju.connected);
1387- assert.strictEqual(juju.get('client'), client);
1388- });
1389-
1390- it('ignores "open" when already open to same client.', function() {
1391- client.receive = function() {
1392- assert.ok(false, 'The receive method should not be called.');
1393- };
1394- // Whitebox test: duplicate "open" state.
1395- juju.connected = true;
1396- juju.set('client', client);
1397- // This is effectively a re-open.
1398- client.open();
1399- // The assert.ok above is the verification.
1400- });
1401-
1402- it('refuses to open if already open to another client.', function() {
1403- // This is a simple way to make sure that we don't leave multiple
1404- // setInterval calls running. If for some reason we want more
1405- // simultaneous clients, that's fine, though that will require
1406- // reworking the delta code generally.
1407- juju.connected = true;
1408- juju.set('client', {receive: function() {
1409- assert.ok(false, 'The receive method should not have been called.');
1410- }});
1411- assert.throws(
1412- client.open.bind(client),
1413- 'INVALID_STATE_ERR : Connection is open to another client.');
1414- });
1415-
1416- it('closes successfully.', function() {
1417- client.open();
1418- assert.isTrue(juju.connected);
1419- assert.notEqual(juju.get('client'), undefined);
1420- client.close();
1421- assert.isFalse(juju.connected);
1422- assert.isUndefined(juju.get('client'));
1423- });
1424-
1425- it('ignores "close" when already closed.', function() {
1426- // This simply shows that we do not raise an error.
1427- juju.close();
1428- });
1429-
1430- it('can dispatch on received information.', function(done) {
1431- var data = {Type: 'TheType', Request: 'TheRequest'};
1432- juju.handleTheTypeTheRequest = function(received) {
1433- assert.notStrictEqual(received, data);
1434- assert.deepEqual(received, data);
1435- done();
1436- };
1437- client.open();
1438- client.send(Y.JSON.stringify(data));
1439- });
1440-
1441- it('refuses to dispatch when closed.', function() {
1442- assert.throws(
1443- juju.receive.bind(juju, {}),
1444- 'INVALID_STATE_ERR : Connection is closed.'
1445- );
1446- });
1447-
1448- it('can log in.', function(done) {
1449- // See FakeBackend's authorizedUsers for these default authentication
1450- // values.
1451- var data = {
1452- Type: 'Admin',
1453- Request: 'Login',
1454- Params: {
1455- AuthTag: 'admin',
1456- Password: 'password'
1457- },
1458- RequestId: 42
1459- };
1460- client.onmessage = function(received) {
1461- // Add in the error indicator so the deepEqual is comparing apples to
1462- // apples.
1463- data.Error = false;
1464- assert.deepEqual(Y.JSON.parse(received.data), data);
1465- assert.isTrue(state.get('authenticated'));
1466- done();
1467- };
1468- state.logout();
1469- assert.isFalse(state.get('authenticated'));
1470- client.open();
1471- client.send(Y.JSON.stringify(data));
1472- });
1473-
1474- it('can log in (environment integration).', function(done) {
1475- state.logout();
1476- env.after('login', function() {
1477- assert.isTrue(env.userIsAuthenticated);
1478- done();
1479- });
1480- env.connect();
1481- env.setCredentials({user: 'admin', password: 'password'});
1482- env.login();
1483- });
1484-
1485- it('can deploy.', function(done) {
1486- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
1487- var data = {
1488- Type: 'Client',
1489- Request: 'ServiceDeploy',
1490- Params: {
1491- CharmUrl: 'cs:wordpress',
1492- ServiceName: 'kumquat',
1493- ConfigYAML: 'funny: business',
1494- NumUnits: 2
1495- },
1496- RequestId: 42
1497- };
1498- client.onmessage = function(received) {
1499- var receivedData = Y.JSON.parse(received.data);
1500- assert.equal(receivedData.RequestId, data.RequestId);
1501- assert.isUndefined(receivedData.Error);
1502- assert.isObject(
1503- state.db.charms.getById('cs:precise/wordpress-10'));
1504- var service = state.db.services.getById('kumquat');
1505- assert.isObject(service);
1506- assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
1507- assert.deepEqual(service.get('config'), {funny: 'business'});
1508- var units = state.db.units.get_units_for_service(service);
1509- assert.lengthOf(units, 2);
1510- done();
1511- };
1512- client.open();
1513- client.send(Y.JSON.stringify(data));
1514- });
1515-
1516- it('can deploy (environment integration).', function() {
1517- env.connect();
1518- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
1519- var callback = function(result) {
1520- assert.isUndefined(result.err);
1521- assert.equal(result.charm_url, 'cs:wordpress');
1522- var service = state.db.services.getById('kumquat');
1523- assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
1524- assert.deepEqual(service.get('config'), {llama: 'pajama'});
1525- };
1526- env.deploy(
1527- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback);
1528- });
1529-
1530- it('can communicate errors after attempting to deploy', function(done) {
1531- env.connect();
1532- state.deploy('cs:wordpress', function() {});
1533- var callback = function(result) {
1534- assert.equal(
1535- result.err, 'A service with this name already exists.');
1536- done();
1537- };
1538- env.deploy('cs:wordpress', undefined, undefined, undefined, 1,
1539- callback);
1540- });
1541-
1542- it('can set a charm.', function(done) {
1543- state.deploy('cs:wordpress', function() {});
1544- var data = {
1545- Type: 'Client',
1546- Request: 'ServiceSetCharm',
1547- Params: {
1548- ServiceName: 'wordpress',
1549- CharmUrl: 'cs:precise/mediawiki-6',
1550- Force: false
1551- },
1552- RequestId: 42
1553- };
1554- client.onmessage = function(received) {
1555- var receivedData = Y.JSON.parse(received.data);
1556- assert.isUndefined(receivedData.err);
1557- var service = state.db.services.getById('wordpress');
1558- assert.equal(service.get('charm'), 'cs:precise/mediawiki-6');
1559- done();
1560- };
1561- client.open();
1562- client.send(Y.JSON.stringify(data));
1563- });
1564-
1565- it('can set a charm (environment integration).', function(done) {
1566- env.connect();
1567- state.deploy('cs:wordpress', function() {});
1568- var callback = function(result) {
1569- assert.isUndefined(result.err);
1570- var service = state.db.services.getById('wordpress');
1571- assert.equal(service.get('charm'), 'cs:precise/mediawiki-6');
1572- done();
1573- };
1574- env.setCharm('wordpress', 'cs:precise/mediawiki-6', false, callback);
1575- });
1576-
1577- /**
1578- Generates the services required for some tests. After the services have
1579- been generated it will call the supplied callback.
1580-
1581- This interacts directly with the fakebackend bypassing the environment.
1582- The test "can add additional units" tests this code directly so as long
1583- as it passes you can consider this method valid.
1584-
1585- @method generateServices
1586- @param {Function} callback The callback to call after the services have
1587- been generated.
1588- */
1589- function generateServices(callback) {
1590- state.deploy('cs:wordpress', function(service) {
1591- var data = {
1592- Type: 'Client',
1593- Request: 'AddServiceUnits',
1594- Params: {
1595- ServiceName: 'wordpress',
1596- NumUnits: 2
1597- }
1598- };
1599- state.nextChanges();
1600- client.onmessage = function(received) {
1601- // After done generating the services
1602- callback(received);
1603- };
1604- client.open();
1605- client.send(Y.JSON.stringify(data));
1606- });
1607- }
1608-
1609- /**
1610- Same as generateServices but uses the environment integration methods.
1611- Should be considered valid if "can add additional units (integration)"
1612- test passes.
1613-
1614- @method generateIntegrationServices
1615- @param {Function} callback The callback to call after the services have
1616- been generated.
1617- */
1618- function generateIntegrationServices(callback) {
1619- var localCb = function(result) {
1620- env.add_unit('kumquat', 2, function(data) {
1621- // After finished generating integrated services.
1622- callback(data);
1623- });
1624- };
1625- env.connect();
1626- env.deploy(
1627- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
1628- }
1629-
1630- /**
1631- Generates the services and then exposes them for the un/expose tests.
1632- After they have been exposed it calls the supplied callback.
1633-
1634- This interacts directly with the fakebackend bypassing the environment and
1635- should be considered valid if "can expose a service" test passes.
1636-
1637- @method generateAndExposeService
1638- @param {Function} callback The callback to call after the services have
1639- been generated.
1640- */
1641- function generateAndExposeService(callback) {
1642- state.deploy('cs:wordpress', function(data) {
1643- var command = {
1644- Type: 'Client',
1645- Request: 'ServiceExpose',
1646- Params: {ServiceName: data.service.get('name')}
1647- };
1648- state.nextChanges();
1649- client.onmessage = function(rec) {
1650- callback(rec);
1651- };
1652- client.open();
1653- client.send(Y.JSON.stringify(command));
1654- }, { unitCount: 1 });
1655- }
1656-
1657- /**
1658- Same as generateAndExposeService but uses the environment integration
1659- methods. Should be considered valid if "can expose a service
1660- (integration)" test passes.
1661-
1662- @method generateAndExposeIntegrationService
1663- @param {Function} callback The callback to call after the services have
1664- been generated.
1665- */
1666- function generateAndExposeIntegrationService(callback) {
1667- var localCb = function(result) {
1668- env.expose(result.service_name, function(rec) {
1669- callback(rec);
1670- });
1671- };
1672- env.connect();
1673- env.deploy(
1674- 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
1675- }
1676-
1677- it('can add additional units', function(done) {
1678- function testForAddedUnits(received) {
1679- var service = state.db.services.getById('wordpress'),
1680- units = state.db.units.get_units_for_service(service),
1681- data = Y.JSON.parse(received.data),
1682- mock = {
1683- Response: {
1684- Units: ['wordpress/1', 'wordpress/2']
1685- }
1686- };
1687- // Do we have enough total units?
1688- assert.lengthOf(units, 3);
1689- // Does the response object contain the proper data
1690- assert.deepEqual(data, mock);
1691- // Error is undefined
1692- assert.isUndefined(data.Error);
1693- done();
1694- }
1695- // Generate the default services and add units
1696- generateServices(testForAddedUnits);
1697- });
1698-
1699- it('throws an error when adding units to an invalid service',
1700- function(done) {
1701- state.deploy('cs:wordpress', function(service) {
1702- var data = {
1703- Type: 'Client',
1704- Request: 'AddServiceUnits',
1705- Params: {
1706- ServiceName: 'noservice',
1707- NumUnits: 2
1708- }
1709- };
1710- state.nextChanges();
1711- client.onmessage = function() {
1712- client.onmessage = function(received) {
1713- var data = Y.JSON.parse(received.data);
1714-
1715- // If there is no error data.err will be undefined
1716- assert.equal(true, !!data.Error);
1717- done();
1718- };
1719- client.send(Y.JSON.stringify(data));
1720- };
1721- client.open();
1722- client.onmessage();
1723- });
1724- }
1725- );
1726-
1727- it('can add additional units (integration)', function(done) {
1728- function testForAddedUnits(data) {
1729- var service = state.db.services.getById('kumquat'),
1730- units = state.db.units.get_units_for_service(service);
1731- assert.lengthOf(units, 3);
1732- done();
1733- }
1734- generateIntegrationServices(testForAddedUnits);
1735- });
1736-
1737- it('can expose a service', function(done) {
1738- function checkExposedService(rec) {
1739- var serviceName = 'wordpress';
1740- var data = Y.JSON.parse(rec.data),
1741- mock = {Response: {}};
1742- var service = state.db.services.getById(serviceName);
1743- assert.equal(service.get('exposed'), true);
1744- assert.deepEqual(data, mock);
1745- done();
1746- }
1747- generateAndExposeService(checkExposedService);
1748- });
1749-
1750- it('can expose a service (integration)', function(done) {
1751- function checkExposedService(rec) {
1752- var service = state.db.services.getById('kumquat');
1753- assert.equal(service.get('exposed'), true);
1754- // The Go API does not set a result value. That is OK as
1755- // it is never used.
1756- assert.isUndefined(rec.result);
1757- done();
1758- }
1759- generateAndExposeIntegrationService(checkExposedService);
1760- });
1761-
1762- it('fails silently when exposing an exposed service', function(done) {
1763- function checkExposedService(rec) {
1764- var service_name = 'wordpress',
1765- data = Y.JSON.parse(rec.data),
1766- service = state.db.services.getById(service_name),
1767- command = {
1768- Type: 'Client',
1769- Request: 'ServiceExpose',
1770- Params: {ServiceName: service_name}
1771- };
1772- state.nextChanges();
1773- client.onmessage = function(rec) {
1774- assert.equal(data.err, undefined);
1775- assert.equal(service.get('exposed'), true);
1776- done();
1777- };
1778- client.send(Y.JSON.stringify(command));
1779- }
1780- generateAndExposeService(checkExposedService);
1781- });
1782-
1783- it('fails with error when exposing an invalid service name',
1784- function(done) {
1785- state.deploy('cs:wordpress', function(data) {
1786- var command = {
1787- Type: 'Client',
1788- Request: 'ServiceExpose',
1789- Params: {ServiceName: 'foobar'}
1790- };
1791- state.nextChanges();
1792- client.onmessage = function(rec) {
1793- var data = Y.JSON.parse(rec.data);
1794- assert.equal(data.Error,
1795- '"foobar" is an invalid service name.');
1796- done();
1797- };
1798- client.open();
1799- client.send(Y.JSON.stringify(command));
1800- }, { unitCount: 1 });
1801- }
1802- );
1803-
1804- it('can unexpose a service', function(done) {
1805- function unexposeService(rec) {
1806- var service_name = 'wordpress',
1807- data = Y.JSON.parse(rec.data),
1808- command = {
1809- Type: 'Client',
1810- Request: 'ServiceUnexpose',
1811- Params: {ServiceName: service_name}
1812- };
1813- state.nextChanges();
1814- client.onmessage = function(rec) {
1815- var data = Y.JSON.parse(rec.data),
1816- service = state.db.services.getById('wordpress'),
1817- mock = {Response: {}};
1818- assert.equal(service.get('exposed'), false);
1819- assert.deepEqual(data, mock);
1820- done();
1821- };
1822- client.send(Y.JSON.stringify(command));
1823- }
1824- generateAndExposeService(unexposeService);
1825- });
1826-
1827- it('can unexpose a service (integration)', function(done) {
1828- var service_name = 'kumquat';
1829- function unexposeService(rec) {
1830- function localCb(rec) {
1831- var service = state.db.services.getById(service_name);
1832- assert.equal(service.get('exposed'), false);
1833- // No result from Go unexpose.
1834- assert.isUndefined(rec.result);
1835- done();
1836- }
1837- env.unexpose(service_name, localCb);
1838- }
1839- generateAndExposeIntegrationService(unexposeService);
1840- });
1841-
1842- it('fails silently when unexposing a not exposed service',
1843- function(done) {
1844- var service_name = 'wordpress';
1845- state.deploy('cs:wordpress', function(data) {
1846- var command = {
1847- Type: 'Client',
1848- Request: 'ServiceUnexpose',
1849- Params: {ServiceName: service_name}
1850- };
1851- state.nextChanges();
1852- client.onmessage = function(rec) {
1853- var data = Y.JSON.parse(rec.data),
1854- service = state.db.services.getById(service_name);
1855- assert.equal(service.get('exposed'), false);
1856- assert.equal(data.err, undefined);
1857- done();
1858- };
1859- client.open();
1860- client.send(Y.JSON.stringify(command));
1861- }, { unitCount: 1 });
1862- }
1863- );
1864-
1865- it('fails with error when unexposing an invalid service name',
1866- function(done) {
1867- function unexposeService(rec) {
1868- var data = Y.JSON.parse(rec.data),
1869- command = {
1870- Type: 'Client',
1871- Request: 'ServiceUnexpose',
1872- Params: {ServiceName: 'foobar'}
1873- };
1874- state.nextChanges();
1875- client.onmessage = function(rec) {
1876- var data = Y.JSON.parse(rec.data);
1877- assert.equal(data.Error, '"foobar" is an invalid service name.');
1878- done();
1879- };
1880- client.send(Y.JSON.stringify(command));
1881- }
1882- generateAndExposeService(unexposeService);
1883- }
1884- );
1885-
1886- it('can add a relation', function(done) {
1887- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
1888- state.deploy('cs:wordpress', function() {
1889- state.deploy('cs:mysql', function() {
1890- var data = {
1891- RequestId: 42,
1892- Type: 'Client',
1893- Request: 'AddRelation',
1894- Params: {
1895- Endpoints: ['wordpress:db', 'mysql:db']
1896- }
1897- };
1898- client.onmessage = function(received) {
1899- var recData = Y.JSON.parse(received.data);
1900- assert.equal(recData.RequestId, data.RequestId);
1901- assert.equal(recData.Error, undefined);
1902- var recEndpoints = recData.Response.Endpoints;
1903- assert.equal(recEndpoints.wordpress.Name, 'db');
1904- assert.equal(recEndpoints.wordpress.Scope, 'global');
1905- assert.equal(recEndpoints.mysql.Name, 'db');
1906- assert.equal(recEndpoints.mysql.Scope, 'global');
1907- done();
1908- };
1909- client.open();
1910- client.send(Y.JSON.stringify(data));
1911- });
1912- });
1913- });
1914-
1915- it('can add a relation (integration)', function(done) {
1916- env.connect();
1917- env.deploy('cs:wordpress', null, null, null, 1, function() {
1918- env.deploy('cs:mysql', null, null, null, 1, function() {
1919- var endpointA = ['wordpress', {name: 'db', role: 'client'}],
1920- endpointB = ['mysql', {name: 'db', role: 'server'}];
1921- env.add_relation(endpointA, endpointB, function(recData) {
1922- assert.equal(recData.err, undefined);
1923- assert.equal(recData.endpoint_a, 'wordpress:db');
1924- assert.equal(recData.endpoint_b, 'mysql:db');
1925- assert.isObject(recData.result);
1926- done();
1927- });
1928- });
1929- });
1930- });
1931-
1932- it('is able to add a relation with a subordinate service', function(done) {
1933- state.deploy('cs:wordpress', function() {
1934- state.deploy('cs:puppet', function(service) {
1935- var data = {
1936- RequestId: 42,
1937- Type: 'Client',
1938- Request: 'AddRelation',
1939- Params: {
1940- Endpoints: ['wordpress:juju-info', 'puppet:juju-info']
1941- }
1942- };
1943- client.onmessage = function(received) {
1944- var recData = Y.JSON.parse(received.data);
1945- assert.equal(recData.RequestId, data.RequestId);
1946- assert.equal(recData.Error, undefined);
1947- var recEndpoints = recData.Response.Endpoints;
1948- assert.equal(recEndpoints.wordpress.Name, 'juju-info');
1949- assert.equal(recEndpoints.wordpress.Scope, 'container');
1950- assert.equal(recEndpoints.puppet.Name, 'juju-info');
1951- assert.equal(recEndpoints.puppet.Scope, 'container');
1952- done();
1953- };
1954- client.open();
1955- client.send(Y.JSON.stringify(data));
1956- });
1957- });
1958- });
1959-
1960- it('throws an error if only one endpoint is supplied', function(done) {
1961- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
1962- state.deploy('cs:wordpress', function() {
1963- var data = {
1964- RequestId: 42,
1965- Type: 'Client',
1966- Request: 'AddRelation',
1967- Params: {
1968- Endpoints: ['wordpress:db']
1969- }
1970- };
1971- client.onmessage = function(received) {
1972- var recData = Y.JSON.parse(received.data);
1973- assert.equal(recData.RequestId, data.RequestId);
1974- assert.equal(recData.Error,
1975- 'Two string endpoint names required to establish a relation');
1976- done();
1977- };
1978- client.open();
1979- client.send(Y.JSON.stringify(data));
1980- });
1981- });
1982-
1983- it('throws an error if endpoints are not relatable', function(done) {
1984- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
1985- state.deploy('cs:wordpress', function() {
1986- var data = {
1987- RequestId: 42,
1988- Type: 'Client',
1989- Request: 'AddRelation',
1990- Params: {
1991- Endpoints: ['wordpress:db', 'mysql:foo']
1992- }
1993- };
1994- client.onmessage = function(received) {
1995- var recData = Y.JSON.parse(received.data);
1996- assert.equal(recData.RequestId, data.RequestId);
1997- assert.equal(recData.Error, 'Charm not loaded.');
1998- done();
1999- };
2000- client.open();
2001- client.send(Y.JSON.stringify(data));
2002- });
2003- });
2004-
2005- it('can remove a relation', function(done) {
2006- // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2007- var relation = ['wordpress:db', 'mysql:db'];
2008- state.deploy('cs:wordpress', function() {
2009- state.deploy('cs:mysql', function() {
2010- state.addRelation(relation[0], relation[1]);
2011- var data = {
2012- RequestId: 42,
2013- Type: 'Client',
2014- Request: 'DestroyRelation',
2015- Params: {
2016- Endpoints: relation
2017- }
2018- };
2019- client.onmessage = function(received) {
2020- var recData = Y.JSON.parse(received.data);
2021- assert.equal(recData.RequestId, data.RequestId);
2022- assert.equal(recData.Error, undefined);
2023- done();
2024- };
2025- client.open();
2026- client.send(Y.JSON.stringify(data));
2027- });
2028- });
2029- });
2030-
2031- it('can remove a relation(integration)', function(done) {
2032- env.connect();
2033- env.deploy('cs:wordpress', null, null, null, 1, function() {
2034- env.deploy('cs:mysql', null, null, null, 1, function() {
2035- var endpointA = ['wordpress', {name: 'db', role: 'client'}],
2036- endpointB = ['mysql', {name: 'db', role: 'server'}];
2037- env.add_relation(endpointA, endpointB, function() {
2038- env.remove_relation(endpointA, endpointB, function(recData) {
2039- assert.equal(recData.err, undefined);
2040- assert.equal(recData.endpoint_a, 'wordpress:db');
2041- assert.equal(recData.endpoint_b, 'mysql:db');
2042- done();
2043- });
2044- });
2045- });
2046- });
2047- });
2048-
2049- });
2050-
2051 })();
2052
2053=== added file 'test/test_sandbox_go.js'
2054--- test/test_sandbox_go.js 1970-01-01 00:00:00 +0000
2055+++ test/test_sandbox_go.js 2013-06-28 15:03:23 +0000
2056@@ -0,0 +1,728 @@
2057+/*
2058+This file is part of the Juju GUI, which lets users view and manage Juju
2059+environments within a graphical interface (https://launchpad.net/juju-gui).
2060+Copyright (C) 2012-2013 Canonical Ltd.
2061+
2062+This program is free software: you can redistribute it and/or modify it under
2063+the terms of the GNU Affero General Public License version 3, as published by
2064+the Free Software Foundation.
2065+
2066+This program is distributed in the hope that it will be useful, but WITHOUT
2067+ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2068+SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
2069+General Public License for more details.
2070+
2071+You should have received a copy of the GNU Affero General Public License along
2072+with this program. If not, see <http://www.gnu.org/licenses/>.
2073+*/
2074+
2075+'use strict';
2076+
2077+(function() {
2078+
2079+ describe('sandbox.GoJujuAPI', function() {
2080+ var requires = [
2081+ 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-go',
2082+ 'juju-models', 'promise'];
2083+ var Y, sandboxModule, ClientConnection, environmentsModule, state, juju,
2084+ client, env, utils;
2085+
2086+ before(function(done) {
2087+ Y = YUI(GlobalConfig).use(requires, function(Y) {
2088+ sandboxModule = Y.namespace('juju.environments.sandbox');
2089+ environmentsModule = Y.namespace('juju.environments');
2090+ utils = Y.namespace('juju-tests.utils');
2091+ // A global variable required for testing.
2092+ window.flags = {};
2093+ done();
2094+ });
2095+ });
2096+
2097+ beforeEach(function() {
2098+ state = utils.makeFakeBackendWithCharmStore();
2099+ juju = new sandboxModule.GoJujuAPI({state: state});
2100+ client = new sandboxModule.ClientConnection({juju: juju});
2101+ env = new environmentsModule.GoEnvironment({conn: client});
2102+ });
2103+
2104+ afterEach(function() {
2105+ env.destroy();
2106+ client.destroy();
2107+ juju.destroy();
2108+ state.destroy();
2109+ });
2110+
2111+ after(function() {
2112+ delete window.flags;
2113+ });
2114+
2115+ it('opens successfully.', function() {
2116+ assert.isFalse(juju.connected);
2117+ assert.isUndefined(juju.get('client'));
2118+ client.open();
2119+ assert.isTrue(juju.connected);
2120+ assert.strictEqual(juju.get('client'), client);
2121+ });
2122+
2123+ it('ignores "open" when already open to same client.', function() {
2124+ client.receive = function() {
2125+ assert.ok(false, 'The receive method should not be called.');
2126+ };
2127+ // Whitebox test: duplicate "open" state.
2128+ juju.connected = true;
2129+ juju.set('client', client);
2130+ // This is effectively a re-open.
2131+ client.open();
2132+ // The assert.ok above is the verification.
2133+ });
2134+
2135+ it('refuses to open if already open to another client.', function() {
2136+ // This is a simple way to make sure that we don't leave multiple
2137+ // setInterval calls running. If for some reason we want more
2138+ // simultaneous clients, that's fine, though that will require
2139+ // reworking the delta code generally.
2140+ juju.connected = true;
2141+ juju.set('client', {receive: function() {
2142+ assert.ok(false, 'The receive method should not have been called.');
2143+ }});
2144+ assert.throws(
2145+ client.open.bind(client),
2146+ 'INVALID_STATE_ERR : Connection is open to another client.');
2147+ });
2148+
2149+ it('closes successfully.', function() {
2150+ client.open();
2151+ assert.isTrue(juju.connected);
2152+ assert.notEqual(juju.get('client'), undefined);
2153+ client.close();
2154+ assert.isFalse(juju.connected);
2155+ assert.isUndefined(juju.get('client'));
2156+ });
2157+
2158+ it('ignores "close" when already closed.', function() {
2159+ // This simply shows that we do not raise an error.
2160+ juju.close();
2161+ });
2162+
2163+ it('can dispatch on received information.', function(done) {
2164+ var data = {Type: 'TheType', Request: 'TheRequest'};
2165+ juju.handleTheTypeTheRequest = function(received) {
2166+ assert.notStrictEqual(received, data);
2167+ assert.deepEqual(received, data);
2168+ done();
2169+ };
2170+ client.open();
2171+ client.send(Y.JSON.stringify(data));
2172+ });
2173+
2174+ it('refuses to dispatch when closed.', function() {
2175+ assert.throws(
2176+ juju.receive.bind(juju, {}),
2177+ 'INVALID_STATE_ERR : Connection is closed.'
2178+ );
2179+ });
2180+
2181+ it('can log in.', function(done) {
2182+ // See FakeBackend's authorizedUsers for these default authentication
2183+ // values.
2184+ var data = {
2185+ Type: 'Admin',
2186+ Request: 'Login',
2187+ Params: {
2188+ AuthTag: 'admin',
2189+ Password: 'password'
2190+ },
2191+ RequestId: 42
2192+ };
2193+ client.onmessage = function(received) {
2194+ // Add in the error indicator so the deepEqual is comparing apples to
2195+ // apples.
2196+ data.Error = false;
2197+ assert.deepEqual(Y.JSON.parse(received.data), data);
2198+ assert.isTrue(state.get('authenticated'));
2199+ done();
2200+ };
2201+ state.logout();
2202+ assert.isFalse(state.get('authenticated'));
2203+ client.open();
2204+ client.send(Y.JSON.stringify(data));
2205+ });
2206+
2207+ it('can log in (environment integration).', function(done) {
2208+ state.logout();
2209+ env.after('login', function() {
2210+ assert.isTrue(env.userIsAuthenticated);
2211+ done();
2212+ });
2213+ env.connect();
2214+ env.setCredentials({user: 'admin', password: 'password'});
2215+ env.login();
2216+ });
2217+
2218+ it('can deploy.', function(done) {
2219+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2220+ var data = {
2221+ Type: 'Client',
2222+ Request: 'ServiceDeploy',
2223+ Params: {
2224+ CharmUrl: 'cs:wordpress',
2225+ ServiceName: 'kumquat',
2226+ ConfigYAML: 'funny: business',
2227+ NumUnits: 2
2228+ },
2229+ RequestId: 42
2230+ };
2231+ client.onmessage = function(received) {
2232+ var receivedData = Y.JSON.parse(received.data);
2233+ assert.equal(receivedData.RequestId, data.RequestId);
2234+ assert.isUndefined(receivedData.Error);
2235+ assert.isObject(
2236+ state.db.charms.getById('cs:precise/wordpress-10'));
2237+ var service = state.db.services.getById('kumquat');
2238+ assert.isObject(service);
2239+ assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
2240+ assert.deepEqual(service.get('config'), {funny: 'business'});
2241+ var units = state.db.units.get_units_for_service(service);
2242+ assert.lengthOf(units, 2);
2243+ done();
2244+ };
2245+ client.open();
2246+ client.send(Y.JSON.stringify(data));
2247+ });
2248+
2249+ it('can deploy (environment integration).', function() {
2250+ env.connect();
2251+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2252+ var callback = function(result) {
2253+ assert.isUndefined(result.err);
2254+ assert.equal(result.charm_url, 'cs:wordpress');
2255+ var service = state.db.services.getById('kumquat');
2256+ assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
2257+ assert.deepEqual(service.get('config'), {llama: 'pajama'});
2258+ };
2259+ env.deploy(
2260+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback);
2261+ });
2262+
2263+ it('can communicate errors after attempting to deploy', function(done) {
2264+ env.connect();
2265+ state.deploy('cs:wordpress', function() {});
2266+ var callback = function(result) {
2267+ assert.equal(
2268+ result.err, 'A service with this name already exists.');
2269+ done();
2270+ };
2271+ env.deploy('cs:wordpress', undefined, undefined, undefined, 1,
2272+ callback);
2273+ });
2274+
2275+ it('can set a charm.', function(done) {
2276+ state.deploy('cs:wordpress', function() {});
2277+ var data = {
2278+ Type: 'Client',
2279+ Request: 'ServiceSetCharm',
2280+ Params: {
2281+ ServiceName: 'wordpress',
2282+ CharmUrl: 'cs:precise/mediawiki-6',
2283+ Force: false
2284+ },
2285+ RequestId: 42
2286+ };
2287+ client.onmessage = function(received) {
2288+ var receivedData = Y.JSON.parse(received.data);
2289+ assert.isUndefined(receivedData.err);
2290+ var service = state.db.services.getById('wordpress');
2291+ assert.equal(service.get('charm'), 'cs:precise/mediawiki-6');
2292+ done();
2293+ };
2294+ client.open();
2295+ client.send(Y.JSON.stringify(data));
2296+ });
2297+
2298+ it('can set a charm (environment integration).', function(done) {
2299+ env.connect();
2300+ state.deploy('cs:wordpress', function() {});
2301+ var callback = function(result) {
2302+ assert.isUndefined(result.err);
2303+ var service = state.db.services.getById('wordpress');
2304+ assert.equal(service.get('charm'), 'cs:precise/mediawiki-6');
2305+ done();
2306+ };
2307+ env.setCharm('wordpress', 'cs:precise/mediawiki-6', false, callback);
2308+ });
2309+
2310+ /**
2311+ Generates the services required for some tests. After the services have
2312+ been generated it will call the supplied callback.
2313+
2314+ This interacts directly with the fakebackend bypassing the environment.
2315+ The test "can add additional units" tests this code directly so as long
2316+ as it passes you can consider this method valid.
2317+
2318+ @method generateServices
2319+ @param {Function} callback The callback to call after the services have
2320+ been generated.
2321+ */
2322+ function generateServices(callback) {
2323+ state.deploy('cs:wordpress', function(service) {
2324+ var data = {
2325+ Type: 'Client',
2326+ Request: 'AddServiceUnits',
2327+ Params: {
2328+ ServiceName: 'wordpress',
2329+ NumUnits: 2
2330+ }
2331+ };
2332+ state.nextChanges();
2333+ client.onmessage = function(received) {
2334+ // After done generating the services
2335+ callback(received);
2336+ };
2337+ client.open();
2338+ client.send(Y.JSON.stringify(data));
2339+ });
2340+ }
2341+
2342+ /**
2343+ Same as generateServices but uses the environment integration methods.
2344+ Should be considered valid if "can add additional units (integration)"
2345+ test passes.
2346+
2347+ @method generateIntegrationServices
2348+ @param {Function} callback The callback to call after the services have
2349+ been generated.
2350+ */
2351+ function generateIntegrationServices(callback) {
2352+ var localCb = function(result) {
2353+ env.add_unit('kumquat', 2, function(data) {
2354+ // After finished generating integrated services.
2355+ callback(data);
2356+ });
2357+ };
2358+ env.connect();
2359+ env.deploy(
2360+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
2361+ }
2362+
2363+ /**
2364+ Generates the services and then exposes them for the un/expose tests.
2365+ After they have been exposed it calls the supplied callback.
2366+
2367+ This interacts directly with the fakebackend bypassing the environment and
2368+ should be considered valid if "can expose a service" test passes.
2369+
2370+ @method generateAndExposeService
2371+ @param {Function} callback The callback to call after the services have
2372+ been generated.
2373+ */
2374+ function generateAndExposeService(callback) {
2375+ state.deploy('cs:wordpress', function(data) {
2376+ var command = {
2377+ Type: 'Client',
2378+ Request: 'ServiceExpose',
2379+ Params: {ServiceName: data.service.get('name')}
2380+ };
2381+ state.nextChanges();
2382+ client.onmessage = function(rec) {
2383+ callback(rec);
2384+ };
2385+ client.open();
2386+ client.send(Y.JSON.stringify(command));
2387+ }, { unitCount: 1 });
2388+ }
2389+
2390+ /**
2391+ Same as generateAndExposeService but uses the environment integration
2392+ methods. Should be considered valid if "can expose a service
2393+ (integration)" test passes.
2394+
2395+ @method generateAndExposeIntegrationService
2396+ @param {Function} callback The callback to call after the services have
2397+ been generated.
2398+ */
2399+ function generateAndExposeIntegrationService(callback) {
2400+ var localCb = function(result) {
2401+ env.expose(result.service_name, function(rec) {
2402+ callback(rec);
2403+ });
2404+ };
2405+ env.connect();
2406+ env.deploy(
2407+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
2408+ }
2409+
2410+ it('can add additional units', function(done) {
2411+ function testForAddedUnits(received) {
2412+ var service = state.db.services.getById('wordpress'),
2413+ units = state.db.units.get_units_for_service(service),
2414+ data = Y.JSON.parse(received.data),
2415+ mock = {
2416+ Response: {
2417+ Units: ['wordpress/1', 'wordpress/2']
2418+ }
2419+ };
2420+ // Do we have enough total units?
2421+ assert.lengthOf(units, 3);
2422+ // Does the response object contain the proper data
2423+ assert.deepEqual(data, mock);
2424+ // Error is undefined
2425+ assert.isUndefined(data.Error);
2426+ done();
2427+ }
2428+ // Generate the default services and add units
2429+ generateServices(testForAddedUnits);
2430+ });
2431+
2432+ it('throws an error when adding units to an invalid service',
2433+ function(done) {
2434+ state.deploy('cs:wordpress', function(service) {
2435+ var data = {
2436+ Type: 'Client',
2437+ Request: 'AddServiceUnits',
2438+ Params: {
2439+ ServiceName: 'noservice',
2440+ NumUnits: 2
2441+ }
2442+ };
2443+ state.nextChanges();
2444+ client.onmessage = function() {
2445+ client.onmessage = function(received) {
2446+ var data = Y.JSON.parse(received.data);
2447+
2448+ // If there is no error data.err will be undefined
2449+ assert.equal(true, !!data.Error);
2450+ done();
2451+ };
2452+ client.send(Y.JSON.stringify(data));
2453+ };
2454+ client.open();
2455+ client.onmessage();
2456+ });
2457+ }
2458+ );
2459+
2460+ it('can add additional units (integration)', function(done) {
2461+ function testForAddedUnits(data) {
2462+ var service = state.db.services.getById('kumquat'),
2463+ units = state.db.units.get_units_for_service(service);
2464+ assert.lengthOf(units, 3);
2465+ done();
2466+ }
2467+ generateIntegrationServices(testForAddedUnits);
2468+ });
2469+
2470+ it('can expose a service', function(done) {
2471+ function checkExposedService(rec) {
2472+ var serviceName = 'wordpress';
2473+ var data = Y.JSON.parse(rec.data),
2474+ mock = {Response: {}};
2475+ var service = state.db.services.getById(serviceName);
2476+ assert.equal(service.get('exposed'), true);
2477+ assert.deepEqual(data, mock);
2478+ done();
2479+ }
2480+ generateAndExposeService(checkExposedService);
2481+ });
2482+
2483+ it('can expose a service (integration)', function(done) {
2484+ function checkExposedService(rec) {
2485+ var service = state.db.services.getById('kumquat');
2486+ assert.equal(service.get('exposed'), true);
2487+ // The Go API does not set a result value. That is OK as
2488+ // it is never used.
2489+ assert.isUndefined(rec.result);
2490+ done();
2491+ }
2492+ generateAndExposeIntegrationService(checkExposedService);
2493+ });
2494+
2495+ it('fails silently when exposing an exposed service', function(done) {
2496+ function checkExposedService(rec) {
2497+ var service_name = 'wordpress',
2498+ data = Y.JSON.parse(rec.data),
2499+ service = state.db.services.getById(service_name),
2500+ command = {
2501+ Type: 'Client',
2502+ Request: 'ServiceExpose',
2503+ Params: {ServiceName: service_name}
2504+ };
2505+ state.nextChanges();
2506+ client.onmessage = function(rec) {
2507+ assert.equal(data.err, undefined);
2508+ assert.equal(service.get('exposed'), true);
2509+ done();
2510+ };
2511+ client.send(Y.JSON.stringify(command));
2512+ }
2513+ generateAndExposeService(checkExposedService);
2514+ });
2515+
2516+ it('fails with error when exposing an invalid service name',
2517+ function(done) {
2518+ state.deploy('cs:wordpress', function(data) {
2519+ var command = {
2520+ Type: 'Client',
2521+ Request: 'ServiceExpose',
2522+ Params: {ServiceName: 'foobar'}
2523+ };
2524+ state.nextChanges();
2525+ client.onmessage = function(rec) {
2526+ var data = Y.JSON.parse(rec.data);
2527+ assert.equal(data.Error,
2528+ '"foobar" is an invalid service name.');
2529+ done();
2530+ };
2531+ client.open();
2532+ client.send(Y.JSON.stringify(command));
2533+ }, { unitCount: 1 });
2534+ }
2535+ );
2536+
2537+ it('can unexpose a service', function(done) {
2538+ function unexposeService(rec) {
2539+ var service_name = 'wordpress',
2540+ data = Y.JSON.parse(rec.data),
2541+ command = {
2542+ Type: 'Client',
2543+ Request: 'ServiceUnexpose',
2544+ Params: {ServiceName: service_name}
2545+ };
2546+ state.nextChanges();
2547+ client.onmessage = function(rec) {
2548+ var data = Y.JSON.parse(rec.data),
2549+ service = state.db.services.getById('wordpress'),
2550+ mock = {Response: {}};
2551+ assert.equal(service.get('exposed'), false);
2552+ assert.deepEqual(data, mock);
2553+ done();
2554+ };
2555+ client.send(Y.JSON.stringify(command));
2556+ }
2557+ generateAndExposeService(unexposeService);
2558+ });
2559+
2560+ it('can unexpose a service (integration)', function(done) {
2561+ var service_name = 'kumquat';
2562+ function unexposeService(rec) {
2563+ function localCb(rec) {
2564+ var service = state.db.services.getById(service_name);
2565+ assert.equal(service.get('exposed'), false);
2566+ // No result from Go unexpose.
2567+ assert.isUndefined(rec.result);
2568+ done();
2569+ }
2570+ env.unexpose(service_name, localCb);
2571+ }
2572+ generateAndExposeIntegrationService(unexposeService);
2573+ });
2574+
2575+ it('fails silently when unexposing a not exposed service',
2576+ function(done) {
2577+ var service_name = 'wordpress';
2578+ state.deploy('cs:wordpress', function(data) {
2579+ var command = {
2580+ Type: 'Client',
2581+ Request: 'ServiceUnexpose',
2582+ Params: {ServiceName: service_name}
2583+ };
2584+ state.nextChanges();
2585+ client.onmessage = function(rec) {
2586+ var data = Y.JSON.parse(rec.data),
2587+ service = state.db.services.getById(service_name);
2588+ assert.equal(service.get('exposed'), false);
2589+ assert.equal(data.err, undefined);
2590+ done();
2591+ };
2592+ client.open();
2593+ client.send(Y.JSON.stringify(command));
2594+ }, { unitCount: 1 });
2595+ }
2596+ );
2597+
2598+ it('fails with error when unexposing an invalid service name',
2599+ function(done) {
2600+ function unexposeService(rec) {
2601+ var data = Y.JSON.parse(rec.data),
2602+ command = {
2603+ Type: 'Client',
2604+ Request: 'ServiceUnexpose',
2605+ Params: {ServiceName: 'foobar'}
2606+ };
2607+ state.nextChanges();
2608+ client.onmessage = function(rec) {
2609+ var data = Y.JSON.parse(rec.data);
2610+ assert.equal(data.Error, '"foobar" is an invalid service name.');
2611+ done();
2612+ };
2613+ client.send(Y.JSON.stringify(command));
2614+ }
2615+ generateAndExposeService(unexposeService);
2616+ }
2617+ );
2618+
2619+ it('can add a relation', function(done) {
2620+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2621+ state.deploy('cs:wordpress', function() {
2622+ state.deploy('cs:mysql', function() {
2623+ var data = {
2624+ RequestId: 42,
2625+ Type: 'Client',
2626+ Request: 'AddRelation',
2627+ Params: {
2628+ Endpoints: ['wordpress:db', 'mysql:db']
2629+ }
2630+ };
2631+ client.onmessage = function(received) {
2632+ var recData = Y.JSON.parse(received.data);
2633+ assert.equal(recData.RequestId, data.RequestId);
2634+ assert.equal(recData.Error, undefined);
2635+ var recEndpoints = recData.Response.Endpoints;
2636+ assert.equal(recEndpoints.wordpress.Name, 'db');
2637+ assert.equal(recEndpoints.wordpress.Scope, 'global');
2638+ assert.equal(recEndpoints.mysql.Name, 'db');
2639+ assert.equal(recEndpoints.mysql.Scope, 'global');
2640+ done();
2641+ };
2642+ client.open();
2643+ client.send(Y.JSON.stringify(data));
2644+ });
2645+ });
2646+ });
2647+
2648+ it('can add a relation (integration)', function(done) {
2649+ env.connect();
2650+ env.deploy('cs:wordpress', null, null, null, 1, function() {
2651+ env.deploy('cs:mysql', null, null, null, 1, function() {
2652+ var endpointA = ['wordpress', {name: 'db', role: 'client'}],
2653+ endpointB = ['mysql', {name: 'db', role: 'server'}];
2654+ env.add_relation(endpointA, endpointB, function(recData) {
2655+ assert.equal(recData.err, undefined);
2656+ assert.equal(recData.endpoint_a, 'wordpress:db');
2657+ assert.equal(recData.endpoint_b, 'mysql:db');
2658+ assert.isObject(recData.result);
2659+ done();
2660+ });
2661+ });
2662+ });
2663+ });
2664+
2665+ it('is able to add a relation with a subordinate service', function(done) {
2666+ state.deploy('cs:wordpress', function() {
2667+ state.deploy('cs:puppet', function(service) {
2668+ var data = {
2669+ RequestId: 42,
2670+ Type: 'Client',
2671+ Request: 'AddRelation',
2672+ Params: {
2673+ Endpoints: ['wordpress:juju-info', 'puppet:juju-info']
2674+ }
2675+ };
2676+ client.onmessage = function(received) {
2677+ var recData = Y.JSON.parse(received.data);
2678+ assert.equal(recData.RequestId, data.RequestId);
2679+ assert.equal(recData.Error, undefined);
2680+ var recEndpoints = recData.Response.Endpoints;
2681+ assert.equal(recEndpoints.wordpress.Name, 'juju-info');
2682+ assert.equal(recEndpoints.wordpress.Scope, 'container');
2683+ assert.equal(recEndpoints.puppet.Name, 'juju-info');
2684+ assert.equal(recEndpoints.puppet.Scope, 'container');
2685+ done();
2686+ };
2687+ client.open();
2688+ client.send(Y.JSON.stringify(data));
2689+ });
2690+ });
2691+ });
2692+
2693+ it('throws an error if only one endpoint is supplied', function(done) {
2694+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2695+ state.deploy('cs:wordpress', function() {
2696+ var data = {
2697+ RequestId: 42,
2698+ Type: 'Client',
2699+ Request: 'AddRelation',
2700+ Params: {
2701+ Endpoints: ['wordpress:db']
2702+ }
2703+ };
2704+ client.onmessage = function(received) {
2705+ var recData = Y.JSON.parse(received.data);
2706+ assert.equal(recData.RequestId, data.RequestId);
2707+ assert.equal(recData.Error,
2708+ 'Two string endpoint names required to establish a relation');
2709+ done();
2710+ };
2711+ client.open();
2712+ client.send(Y.JSON.stringify(data));
2713+ });
2714+ });
2715+
2716+ it('throws an error if endpoints are not relatable', function(done) {
2717+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2718+ state.deploy('cs:wordpress', function() {
2719+ var data = {
2720+ RequestId: 42,
2721+ Type: 'Client',
2722+ Request: 'AddRelation',
2723+ Params: {
2724+ Endpoints: ['wordpress:db', 'mysql:foo']
2725+ }
2726+ };
2727+ client.onmessage = function(received) {
2728+ var recData = Y.JSON.parse(received.data);
2729+ assert.equal(recData.RequestId, data.RequestId);
2730+ assert.equal(recData.Error, 'Charm not loaded.');
2731+ done();
2732+ };
2733+ client.open();
2734+ client.send(Y.JSON.stringify(data));
2735+ });
2736+ });
2737+
2738+ it('can remove a relation', function(done) {
2739+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
2740+ var relation = ['wordpress:db', 'mysql:db'];
2741+ state.deploy('cs:wordpress', function() {
2742+ state.deploy('cs:mysql', function() {
2743+ state.addRelation(relation[0], relation[1]);
2744+ var data = {
2745+ RequestId: 42,
2746+ Type: 'Client',
2747+ Request: 'DestroyRelation',
2748+ Params: {
2749+ Endpoints: relation
2750+ }
2751+ };
2752+ client.onmessage = function(received) {
2753+ var recData = Y.JSON.parse(received.data);
2754+ assert.equal(recData.RequestId, data.RequestId);
2755+ assert.equal(recData.Error, undefined);
2756+ done();
2757+ };
2758+ client.open();
2759+ client.send(Y.JSON.stringify(data));
2760+ });
2761+ });
2762+ });
2763+
2764+ it('can remove a relation(integration)', function(done) {
2765+ env.connect();
2766+ env.deploy('cs:wordpress', null, null, null, 1, function() {
2767+ env.deploy('cs:mysql', null, null, null, 1, function() {
2768+ var endpointA = ['wordpress', {name: 'db', role: 'client'}],
2769+ endpointB = ['mysql', {name: 'db', role: 'server'}];
2770+ env.add_relation(endpointA, endpointB, function() {
2771+ env.remove_relation(endpointA, endpointB, function(recData) {
2772+ assert.equal(recData.err, undefined);
2773+ assert.equal(recData.endpoint_a, 'wordpress:db');
2774+ assert.equal(recData.endpoint_b, 'mysql:db');
2775+ done();
2776+ });
2777+ });
2778+ });
2779+ });
2780+ });
2781+
2782+ });
2783+
2784+})();
2785
2786=== added file 'test/test_sandbox_python.js'
2787--- test/test_sandbox_python.js 1970-01-01 00:00:00 +0000
2788+++ test/test_sandbox_python.js 2013-06-28 15:03:23 +0000
2789@@ -0,0 +1,1347 @@
2790+/*
2791+This file is part of the Juju GUI, which lets users view and manage Juju
2792+environments within a graphical interface (https://launchpad.net/juju-gui).
2793+Copyright (C) 2012-2013 Canonical Ltd.
2794+
2795+This program is free software: you can redistribute it and/or modify it under
2796+the terms of the GNU Affero General Public License version 3, as published by
2797+the Free Software Foundation.
2798+
2799+This program is distributed in the hope that it will be useful, but WITHOUT
2800+ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
2801+SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
2802+General Public License for more details.
2803+
2804+You should have received a copy of the GNU Affero General Public License along
2805+with this program. If not, see <http://www.gnu.org/licenses/>.
2806+*/
2807+
2808+'use strict';
2809+
2810+(function() {
2811+
2812+ describe('sandbox.PyJujuAPI', function() {
2813+ var requires = [
2814+ 'juju-env-sandbox', 'juju-tests-utils', 'juju-env-python',
2815+ 'juju-models', 'promise'];
2816+ var Y, sandboxModule, ClientConnection, environmentsModule, state, juju,
2817+ client, env, utils, cleanups;
2818+
2819+ before(function(done) {
2820+ Y = YUI(GlobalConfig).use(requires, function(Y) {
2821+ sandboxModule = Y.namespace('juju.environments.sandbox');
2822+ environmentsModule = Y.namespace('juju.environments');
2823+ utils = Y.namespace('juju-tests.utils');
2824+ // A global variable required for testing.
2825+ window.flags = {};
2826+ done();
2827+ });
2828+ });
2829+
2830+ beforeEach(function() {
2831+ state = utils.makeFakeBackendWithCharmStore();
2832+ juju = new sandboxModule.PyJujuAPI({state: state});
2833+ client = new sandboxModule.ClientConnection({juju: juju});
2834+ env = new environmentsModule.PythonEnvironment({conn: client});
2835+ cleanups = [];
2836+ });
2837+
2838+ afterEach(function() {
2839+ Y.each(cleanups, function(f) {f();});
2840+ env.destroy();
2841+ client.destroy();
2842+ juju.destroy();
2843+ state.destroy();
2844+ });
2845+
2846+ after(function() {
2847+ delete window.flags;
2848+ });
2849+
2850+ /**
2851+ Generates the services required for some tests. After the services have
2852+ been generated it will call the supplied callback.
2853+
2854+ This interacts directly with the fakebackend bypassing the environment.
2855+ The test "can add additional units" tests this code directly so as long
2856+ as it passes you can consider this method valid.
2857+
2858+ @method generateServices
2859+ @param {Function} callback The callback to call after the services have
2860+ been generated.
2861+ */
2862+ function generateServices(callback) {
2863+ state.deploy('cs:wordpress', function(service) {
2864+ var data = {
2865+ op: 'add_unit',
2866+ service_name: 'wordpress',
2867+ num_units: 2
2868+ };
2869+ state.nextChanges();
2870+ client.onmessage = function() {
2871+ client.onmessage = function(received) {
2872+ // After done generating the services
2873+ callback(received);
2874+ };
2875+ client.send(Y.JSON.stringify(data));
2876+ };
2877+ client.open();
2878+ });
2879+ }
2880+
2881+ /**
2882+ Generates the two services required for relation removal tests. After the
2883+ services have been generated, a relation between them will be added and
2884+ then removed.
2885+
2886+ This interacts directly with the fakebackend bypassing the environment.
2887+
2888+ @method generateAndRelateServices
2889+ @param {Array} charms The URLs of two charms to be deployed.
2890+ @param {Array} relation Two endpoint strings to be related.
2891+ @param {Array} removeRelation Two enpoint strings identifying
2892+ a relation to be removed.
2893+ @param {Object} mock Object with the expected return values of
2894+ the relation removal operation.
2895+ @param {Function} done To be called to signal the test end.
2896+ @return {undefined} Side effects only.
2897+ */
2898+ function generateAndRelateServices(charms, relation,
2899+ removeRelation, mock, done) {
2900+ state.deploy(charms[0], function() {
2901+ state.deploy(charms[1], function() {
2902+ if (relation) {
2903+ state.addRelation(relation[0], relation[1]);
2904+ }
2905+ var data = {
2906+ op: 'remove_relation',
2907+ endpoint_a: removeRelation[0],
2908+ endpoint_b: removeRelation[1]
2909+ };
2910+ client.onmessage = function(received) {
2911+ var recData = Y.JSON.parse(received.data);
2912+ // Skip the defaultSeriesChange message.
2913+ if (recData.default_series === undefined) {
2914+ assert.equal(recData.result, mock.result);
2915+ assert.equal(recData.err, mock.err);
2916+ if (!recData.err) {
2917+ assert.equal(recData.endpoint_a, mock.endpoint_a);
2918+ assert.equal(recData.endpoint_b, mock.endpoint_b);
2919+ }
2920+ done();
2921+ }
2922+ };
2923+ client.open();
2924+ client.send(Y.JSON.stringify(data));
2925+ });
2926+ });
2927+ }
2928+
2929+ /**
2930+ Same as generateServices but uses the environment integration methods.
2931+ Should be considered valid if "can add additional units (integration)"
2932+ test passes.
2933+
2934+ @method generateIntegrationServices
2935+ @param {Function} callback The callback to call after the services have
2936+ been generated.
2937+ */
2938+ function generateIntegrationServices(callback) {
2939+ env.after('defaultSeriesChange', function() {
2940+ var localCb = function(result) {
2941+ env.add_unit('kumquat', 2, function(data) {
2942+ // After finished generating integrated services
2943+ callback(data);
2944+ });
2945+ };
2946+ env.deploy(
2947+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
2948+ });
2949+ env.connect();
2950+ }
2951+
2952+ /**
2953+ Generates the services and then exposes them for the un/expose tests.
2954+ After they have been exposed it calls the supplied callback.
2955+
2956+ This interacts directly with the fakebackend bypassing the environment and
2957+ should be considered valid if "can expose a service" test passes.
2958+
2959+ @method generateAndExposeService
2960+ @param {Function} callback The callback to call after the services have
2961+ been generated.
2962+ */
2963+ function generateAndExposeService(callback) {
2964+ state.deploy('cs:wordpress', function(data) {
2965+ var command = {
2966+ op: 'expose',
2967+ service_name: data.service.get('name')
2968+ };
2969+ state.nextChanges();
2970+ client.onmessage = function() {
2971+ client.onmessage = function(rec) {
2972+ callback(rec);
2973+ };
2974+ client.send(Y.JSON.stringify(command));
2975+ };
2976+ client.open();
2977+ }, { unitCount: 1 });
2978+ }
2979+
2980+ /**
2981+ Same as generateAndExposeService but uses the environment integration
2982+ methods. Should be considered valid if "can expose a service
2983+ (integration)" test passes.
2984+
2985+ @method generateAndExposeIntegrationService
2986+ @param {Function} callback The callback to call after the services have
2987+ been generated.
2988+ */
2989+ function generateAndExposeIntegrationService(callback) {
2990+ env.after('defaultSeriesChange', function() {
2991+ var localCb = function(result) {
2992+ env.expose(result.service_name, function(rec) {
2993+ callback(rec);
2994+ });
2995+ };
2996+ env.deploy(
2997+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, localCb);
2998+ });
2999+ env.connect();
3000+ }
3001+
3002+ it('opens successfully.', function(done) {
3003+ var isAsync = false;
3004+ client.onmessage = function(message) {
3005+ assert.isTrue(isAsync);
3006+ assert.deepEqual(
3007+ Y.JSON.parse(message.data),
3008+ {
3009+ ready: true,
3010+ provider_type: 'demonstration',
3011+ default_series: 'precise'
3012+ });
3013+ done();
3014+ };
3015+ assert.isFalse(juju.connected);
3016+ assert.isUndefined(juju.get('client'));
3017+ client.open();
3018+ assert.isTrue(juju.connected);
3019+ assert.strictEqual(juju.get('client'), client);
3020+ isAsync = true;
3021+ });
3022+
3023+ it('ignores "open" when already open to same client.', function() {
3024+ client.receive = function() {
3025+ assert.ok(false, 'The receive method should not be called.');
3026+ };
3027+ // Whitebox test: duplicate "open" state.
3028+ juju.connected = true;
3029+ juju.set('client', client);
3030+ // This is effectively a re-open.
3031+ client.open();
3032+ // The assert.ok above is the verification.
3033+ });
3034+
3035+ it('refuses to open if already open to another client.', function() {
3036+ // This is a simple way to make sure that we don't leave multiple
3037+ // setInterval calls running. If for some reason we want more
3038+ // simultaneous clients, that's fine, though that will require
3039+ // reworking the delta code generally.
3040+ juju.connected = true;
3041+ juju.set('client', {receive: function() {
3042+ assert.ok(false, 'The receive method should not have been called.');
3043+ }});
3044+ assert.throws(
3045+ client.open.bind(client),
3046+ 'INVALID_STATE_ERR : Connection is open to another client.');
3047+ });
3048+
3049+ it('closes successfully.', function(done) {
3050+ client.onmessage = function() {
3051+ client.close();
3052+ assert.isFalse(juju.connected);
3053+ assert.isUndefined(juju.get('client'));
3054+ done();
3055+ };
3056+ client.open();
3057+ });
3058+
3059+ it('ignores "close" when already closed.', function() {
3060+ // This simply shows that we do not raise an error.
3061+ juju.close();
3062+ });
3063+
3064+ it('can dispatch on received information.', function(done) {
3065+ var data = {op: 'testingTesting123', foo: 'bar'};
3066+ juju.performOp_testingTesting123 = function(received) {
3067+ assert.notStrictEqual(received, data);
3068+ assert.deepEqual(received, data);
3069+ done();
3070+ };
3071+ client.open();
3072+ client.send(Y.JSON.stringify(data));
3073+ });
3074+
3075+ it('refuses to dispatch when closed.', function() {
3076+ assert.throws(
3077+ juju.receive.bind(juju, {}),
3078+ 'INVALID_STATE_ERR : Connection is closed.'
3079+ );
3080+ });
3081+
3082+ it('can log in.', function(done) {
3083+ state.logout();
3084+ // See FakeBackend's authorizedUsers for these default authentication
3085+ // values.
3086+ var data = {
3087+ op: 'login',
3088+ user: 'admin',
3089+ password: 'password',
3090+ request_id: 42
3091+ };
3092+ client.onmessage = function(received) {
3093+ // First message is the provider type and default series. We ignore
3094+ // it, and prepare for the next one, which will be the reply to our
3095+ // login.
3096+ client.onmessage = function(received) {
3097+ data.result = true;
3098+ assert.deepEqual(Y.JSON.parse(received.data), data);
3099+ assert.isTrue(state.get('authenticated'));
3100+ done();
3101+ };
3102+ client.send(Y.JSON.stringify(data));
3103+ };
3104+ client.open();
3105+ });
3106+
3107+ it('can log in (environment integration).', function(done) {
3108+ state.logout();
3109+ env.after('defaultSeriesChange', function() {
3110+ // See FakeBackend's authorizedUsers for these default values.
3111+ env.setCredentials({user: 'admin', password: 'password'});
3112+ env.after('login', function() {
3113+ assert.isTrue(env.userIsAuthenticated);
3114+ done();
3115+ });
3116+ env.login();
3117+ });
3118+ env.connect();
3119+ });
3120+
3121+ it('can deploy.', function(done) {
3122+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
3123+ var data = {
3124+ op: 'deploy',
3125+ charm_url: 'cs:wordpress',
3126+ service_name: 'kumquat',
3127+ config_raw: 'funny: business',
3128+ num_units: 2,
3129+ request_id: 42
3130+ };
3131+ client.onmessage = function(received) {
3132+ // First message is the provider type and default series. We ignore
3133+ // it, and prepare for the next one, which will be the reply to our
3134+ // deployment.
3135+ client.onmessage = function(received) {
3136+ var parsed = Y.JSON.parse(received.data);
3137+ assert.isUndefined(parsed.err);
3138+ assert.deepEqual(parsed, data);
3139+ assert.isObject(
3140+ state.db.charms.getById('cs:precise/wordpress-10'));
3141+ var service = state.db.services.getById('kumquat');
3142+ assert.isObject(service);
3143+ assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
3144+ assert.deepEqual(service.get('config'), {funny: 'business'});
3145+ var units = state.db.units.get_units_for_service(service);
3146+ assert.lengthOf(units, 2);
3147+ done();
3148+ };
3149+ client.send(Y.JSON.stringify(data));
3150+ };
3151+ client.open();
3152+ });
3153+
3154+ it('can deploy (environment integration).', function(done) {
3155+ // We begin logged in. See utils.makeFakeBackendWithCharmStore.
3156+ env.after('defaultSeriesChange', function() {
3157+ var callback = function(result) {
3158+ assert.isUndefined(result.err);
3159+ assert.equal(result.charm_url, 'cs:wordpress');
3160+ var service = state.db.services.getById('kumquat');
3161+ assert.equal(service.get('charm'), 'cs:precise/wordpress-10');
3162+ assert.deepEqual(service.get('config'), {llama: 'pajama'});
3163+ done();
3164+ };
3165+ env.deploy(
3166+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, callback);
3167+ });
3168+ env.connect();
3169+ });
3170+
3171+ it('can communicate errors after attempting to deploy', function(done) {
3172+ // Create a service with the name "wordpress".
3173+ // The charm store is synchronous in tests, so we don't need a real
3174+ // callback.
3175+ state.deploy('cs:wordpress', function() {});
3176+ env.after('defaultSeriesChange', function() {
3177+ var callback = function(result) {
3178+ assert.equal(
3179+ result.err, 'A service with this name already exists.');
3180+ done();
3181+ };
3182+ env.deploy(
3183+ 'cs:wordpress', undefined, undefined, undefined, 1, callback);
3184+ });
3185+ env.connect();
3186+ });
3187+
3188+ it('can send a delta stream of changes.', function(done) {
3189+ // Create a service with the name "wordpress".
3190+ // The charm store is synchronous in tests, so we don't need a real
3191+ // callback.
3192+ state.deploy('cs:wordpress', function() {});
3193+ client.onmessage = function(received) {
3194+ // First message is the provider type and default series. We ignore
3195+ // it, and prepare for the next one, which will handle the delta
3196+ // stream.
3197+ client.onmessage = function(received) {
3198+ var parsed = Y.JSON.parse(received.data);
3199+ assert.equal(parsed.op, 'delta');
3200+ var deltas = parsed.result;
3201+ assert.lengthOf(deltas, 3);
3202+ assert.equal(deltas[0][0], 'service');
3203+ assert.equal(deltas[0][1], 'change');
3204+ assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10');
3205+ assert.equal(deltas[1][0], 'machine');
3206+ assert.equal(deltas[1][1], 'change');
3207+ assert.equal(deltas[2][0], 'unit');
3208+ assert.equal(deltas[2][1], 'change');
3209+ done();
3210+ };
3211+ juju.sendDelta();
3212+ };
3213+ client.open();
3214+ });
3215+
3216+ it('does not send a delta if there are no changes.', function(done) {
3217+ client.onmessage = function(received) {
3218+ // First message is the provider type and default series. We ignore
3219+ // it, and prepare for the next one, which will handle the delta
3220+ // stream.
3221+ client.receiveNow = function(response) {
3222+ assert.ok(false, 'This method should not have been called.');
3223+ };
3224+ juju.sendDelta();
3225+ done();
3226+ };
3227+ client.open();
3228+ });
3229+
3230+ it('can send a delta stream (integration).', function(done) {
3231+ // Create a service with the name "wordpress".
3232+ // The charm store is synchronous in tests, so we don't need a real
3233+ // callback.
3234+ state.deploy('cs:wordpress', function() {}, {unitCount: 2});
3235+ var db = new Y.juju.models.Database();
3236+ db.on('update', function() {
3237+ // We want to verify that the GUI database is equivalent to the state
3238+ // database.
3239+ assert.equal(db.services.size(), 1);
3240+ assert.equal(db.units.size(), 2);
3241+ assert.equal(db.machines.size(), 2);
3242+ var stateService = state.db.services.item(0);
3243+ var guiService = db.services.item(0);
3244+ Y.each(
3245+ ['charm', 'config', 'constraints', 'exposed',
3246+ 'id', 'name', 'subordinate'],
3247+ function(attrName) {
3248+ assert.deepEqual(
3249+ guiService.get(attrName), stateService.get(attrName));
3250+ }
3251+ );
3252+ state.db.units.each(function(stateUnit) {
3253+ var guiUnit = db.units.getById(stateUnit.id);
3254+ Y.each(
3255+ ['agent_state', 'machine', 'number', 'service'],
3256+ function(attrName) {
3257+ assert.deepEqual(guiUnit[attrName], stateUnit[attrName]);
3258+ }
3259+ );
3260+ });
3261+ state.db.machines.each(function(stateMachine) {
3262+ var guiMachine = db.machines.getById(stateMachine.id);
3263+ Y.each(
3264+ ['agent_state', 'public_address', 'machine_id'],
3265+ function(attrName) {
3266+ assert.deepEqual(guiMachine[attrName], stateMachine[attrName]);
3267+ }
3268+ );
3269+ });
3270+ done();
3271+ });
3272+ env.on('delta', db.onDelta, db);
3273+ env.after('defaultSeriesChange', function() {juju.sendDelta();});
3274+ env.connect();
3275+ });
3276+
3277+ it('sends delta streams periodically after opening.', function(done) {
3278+ client.onmessage = function(received) {
3279+ // First message is the provider type and default series. We ignore
3280+ // it, and prepare for the next one, which will handle the delta
3281+ // stream.
3282+ var isAsync = false;
3283+ client.onmessage = function(received) {
3284+ assert.isTrue(isAsync);
3285+ var parsed = Y.JSON.parse(received.data);
3286+ assert.equal(parsed.op, 'delta');
3287+ var deltas = parsed.result;
3288+ assert.lengthOf(deltas, 3);
3289+ assert.equal(deltas[0][2].charm, 'cs:precise/wordpress-10');
3290+ done();
3291+ };
3292+ // Create a service with the name "wordpress".
3293+ // The charm store is synchronous in tests, so we don't need a real
3294+ // callback.
3295+ state.deploy('cs:wordpress', function() {});
3296+ isAsync = true;
3297+ };
3298+ juju.set('deltaInterval', 4);
3299+ client.open();
3300+ });
3301+
3302+ it('stops sending delta streams after closing.', function(done) {
3303+ var sysSetInterval = window.setInterval;
3304+ var sysClearInterval = window.clearInterval;
3305+ cleanups.push(function() {
3306+ window.setInterval = sysSetInterval;
3307+ window.clearInterval = sysClearInterval;
3308+ });
3309+ window.setInterval = function(f, interval) {
3310+ assert.isFunction(f);
3311+ assert.equal(interval, 4);
3312+ return 42;
3313+ };
3314+ window.clearInterval = function(token) {
3315+ assert.equal(token, 42);
3316+ done();
3317+ };
3318+ client.onmessage = function(received) {
3319+ // First message is the provider type and default series. We can
3320+ // close now.
3321+ client.close();
3322+ };
3323+ juju.set('deltaInterval', 4);
3324+ client.open();
3325+ });
3326+
3327+ it('can add additional units', function(done) {
3328+ function testForAddedUnits(received) {
3329+ var service = state.db.services.getById('wordpress'),
3330+ units = state.db.units.get_units_for_service(service),
3331+ data = Y.JSON.parse(received.data),
3332+ mock = {
3333+ num_units: 2,
3334+ service_name: 'wordpress',
3335+ op: 'add_unit',
3336+ result: ['wordpress/1', 'wordpress/2']
3337+ };
3338+ // Do we have enough total units?
3339+ assert.lengthOf(units, 3);
3340+ // Does the response object contain the proper data
3341+ assert.deepEqual(data, mock);
3342+ // Error is undefined
3343+ assert.isUndefined(data.err);
3344+ done();
3345+ }
3346+ // Generate the default services and add units
3347+ generateServices(testForAddedUnits);
3348+ });
3349+
3350+ it('throws an error when adding units to an invalid service',
3351+ function(done) {
3352+ state.deploy('cs:wordpress', function(service) {
3353+ var data = {
3354+ op: 'add_unit',
3355+ service_name: 'noservice',
3356+ num_units: 2
3357+ };
3358+ //Clear out the delta stream
3359+ state.nextChanges();
3360+ client.onmessage = function() {
3361+ client.onmessage = function(received) {
3362+ var data = Y.JSON.parse(received.data);
3363+
3364+ // If there is no error data.err will be undefined
3365+ assert.equal(true, !!data.err);
3366+ done();
3367+ };
3368+ client.send(Y.JSON.stringify(data));
3369+ };
3370+ client.open();
3371+ });
3372+ }
3373+ );
3374+
3375+ it('can add additional units (integration)', function(done) {
3376+ function testForAddedUnits(data) {
3377+ var service = state.db.services.getById('kumquat'),
3378+ units = state.db.units.get_units_for_service(service);
3379+ assert.lengthOf(units, 3);
3380+ done();
3381+ }
3382+ generateIntegrationServices(testForAddedUnits);
3383+ });
3384+
3385+ it('can remove units', function(done) {
3386+ function removeUnits() {
3387+ var data = {
3388+ op: 'remove_units',
3389+ unit_names: ['wordpress/0', 'wordpress/1']
3390+ };
3391+ client.onmessage = function(rec) {
3392+ var data = Y.JSON.parse(rec.data),
3393+ mock = {
3394+ op: 'remove_units',
3395+ result: true,
3396+ unit_names: ['wordpress/0', 'wordpress/1']
3397+ };
3398+ // No errors
3399+ assert.equal(data.result, true);
3400+ // Returned data object contains all information
3401+ assert.deepEqual(data, mock);
3402+ done();
3403+ };
3404+ client.send(Y.JSON.stringify(data));
3405+ }
3406+ // Generate the services base data and then execute the test
3407+ generateServices(removeUnits);
3408+ });
3409+
3410+ it('can remove units (integration)', function(done) {
3411+ function removeUnits() {
3412+ var unitNames = ['kumquat/1', 'kumquat/2'];
3413+ env.remove_units(unitNames, function(data) {
3414+ assert.equal(data.result, true);
3415+ assert.deepEqual(data.unit_names, unitNames);
3416+ done();
3417+ });
3418+ }
3419+ // Generate the services via the integration method then execute the test
3420+ generateIntegrationServices(removeUnits);
3421+ });
3422+
3423+ it('allows attempting to remove units from an invalid service',
3424+ function(done) {
3425+ function removeUnit() {
3426+ var data = {
3427+ op: 'remove_units',
3428+ unit_names: ['bar/2']
3429+ };
3430+ client.onmessage = function(rec) {
3431+ var data = Y.JSON.parse(rec.data);
3432+ assert.equal(data.result, true);
3433+ done();
3434+ };
3435+ client.send(Y.JSON.stringify(data));
3436+ }
3437+ // Generate the services base data then execute the test.
3438+ generateServices(removeUnit);
3439+ }
3440+ );
3441+
3442+ it('throws an error if unit is a subordinate', function(done) {
3443+ function removeUnits() {
3444+ var data = {
3445+ op: 'remove_units',
3446+ unit_names: ['wordpress/1']
3447+ };
3448+ client.onmessage = function(rec) {
3449+ var data = Y.JSON.parse(rec.data);
3450+ assert.equal(Y.Lang.isArray(data.err), true);
3451+ assert.equal(data.err.length, 1);
3452+ done();
3453+ };
3454+ state.db.services.getById('wordpress').set('is_subordinate', true);
3455+ client.send(Y.JSON.stringify(data));
3456+ }
3457+ // Generate the services base data then execute the test.
3458+ generateServices(removeUnits);
3459+ });
3460+
3461+ it('can get a service', function(done) {
3462+ generateServices(function(data) {
3463+ // Post deploy of wordpress so we should be able to
3464+ // pull its data.
3465+ var op = {
3466+ op: 'get_service',
3467+ service_name: 'wordpress',
3468+ request_id: 99
3469+ };
3470+ client.onmessage = function(received) {
3471+ var parsed = Y.JSON.parse(received.data);
3472+ var service = parsed.result;
3473+ assert.equal(service.name, 'wordpress');
3474+ // Error should be undefined.
3475+ done(received.error);
3476+ };
3477+ client.send(Y.JSON.stringify(op));
3478+ });
3479+ });
3480+
3481+ it('can destroy a service', function(done) {
3482+ generateServices(function(data) {
3483+ // Post deploy of wordpress so we should be able to
3484+ // destroy it.
3485+ var op = {
3486+ op: 'destroy_service',
3487+ service_name: 'wordpress',
3488+ request_id: 99
3489+ };
3490+ client.onmessage = function(received) {
3491+ var parsed = Y.JSON.parse(received.data);
3492+ assert.equal(parsed.result, 'wordpress');
3493+ // Error should be undefined.
3494+ done(received.error);
3495+ };
3496+ client.send(Y.JSON.stringify(op));
3497+ });
3498+ });
3499+
3500+ it('can destroy a service (integration)', function(done) {
3501+ function destroyService(rec) {
3502+ function localCb(rec2) {
3503+ assert.equal(rec2.result, 'kumquat');
3504+ var service = state.db.services.getById('kumquat');
3505+ assert.isNull(service);
3506+ done();
3507+ }
3508+ var result = env.destroy_service(rec.service_name, localCb);
3509+ }
3510+ generateAndExposeIntegrationService(destroyService);
3511+ });
3512+
3513+ it('can get a charm', function(done) {
3514+ generateServices(function(data) {
3515+ // Post deploy of wordpress we should be able to
3516+ // pull its data.
3517+ var op = {
3518+ op: 'get_charm',
3519+ charm_url: 'cs:wordpress',
3520+ request_id: 99
3521+ };
3522+ client.onmessage = function(received) {
3523+ var parsed = Y.JSON.parse(received.data);
3524+ var charm = parsed.result;
3525+ assert.equal(charm.name, 'wordpress');
3526+ // Error should be undefined.
3527+ done(received.error);
3528+ };
3529+ client.send(Y.JSON.stringify(op));
3530+ });
3531+ });
3532+
3533+ it('can set service config', function(done) {
3534+ generateServices(function(data) {
3535+ // Post deploy of wordpress we should be able to
3536+ // pull its data.
3537+ var op = {
3538+ op: 'set_config',
3539+ service_name: 'wordpress',
3540+ config: {'blog-title': 'Inimical'},
3541+ request_id: 99
3542+ };
3543+ client.onmessage = function(received) {
3544+ var parsed = Y.JSON.parse(received.data);
3545+ assert.deepEqual(parsed.result, {'blog-title': 'Inimical'});
3546+ var service = state.db.services.getById('wordpress');
3547+ assert.equal(service.get('config')['blog-title'], 'Inimical');
3548+ // Error should be undefined.
3549+ done(parsed.error);
3550+ };
3551+ client.send(Y.JSON.stringify(op));
3552+ });
3553+ });
3554+
3555+ it('can set service constraints', function(done) {
3556+ generateServices(function(data) {
3557+ // Post deploy of wordpress we should be able to
3558+ // pull its data.
3559+ var op = {
3560+ op: 'set_constraints',
3561+ service_name: 'wordpress',
3562+ constraints: ['cpu=2', 'mem=128'],
3563+ request_id: 99
3564+ };
3565+ client.onmessage = function(received) {
3566+ var service = state.db.services.getById('wordpress');
3567+ var constraints = service.get('constraints');
3568+ assert.equal(constraints.cpu, '2');
3569+ assert.equal(constraints.mem, '128');
3570+ // Error should be undefined.
3571+ done(received.error);
3572+ };
3573+ client.send(Y.JSON.stringify(op));
3574+ });
3575+ });
3576+
3577+ it('can expose a service', function(done) {
3578+ function checkExposedService(rec) {
3579+ var data = Y.JSON.parse(rec.data),
3580+ mock = {
3581+ op: 'expose',
3582+ result: true,
3583+ service_name: 'wordpress'
3584+ };
3585+ var service = state.db.services.getById(mock.service_name);
3586+ assert.equal(service.get('exposed'), true);
3587+ assert.equal(data.result, true);
3588+ assert.deepEqual(data, mock);
3589+ done();
3590+ }
3591+ generateAndExposeService(checkExposedService);
3592+ });
3593+
3594+ it('can expose a service (integration)', function(done) {
3595+ function checkExposedService(rec) {
3596+ var service = state.db.services.getById('kumquat');
3597+ assert.equal(service.get('exposed'), true);
3598+ assert.equal(rec.result, true);
3599+ done();
3600+ }
3601+ generateAndExposeIntegrationService(checkExposedService);
3602+ });
3603+
3604+ it('fails silently when exposing an exposed service', function(done) {
3605+ function checkExposedService(rec) {
3606+ var data = Y.JSON.parse(rec.data),
3607+ service = state.db.services.getById(data.service_name),
3608+ command = {
3609+ op: 'expose',
3610+ service_name: data.service_name
3611+ };
3612+ state.nextChanges();
3613+ client.onmessage = function(rec) {
3614+ assert.equal(data.err, undefined);
3615+ assert.equal(service.get('exposed'), true);
3616+ assert.equal(data.result, true);
3617+ done();
3618+ };
3619+ client.send(Y.JSON.stringify(command));
3620+ }
3621+ generateAndExposeService(checkExposedService);
3622+ });
3623+
3624+ it('fails with error when exposing an invalid service name',
3625+ function(done) {
3626+ state.deploy('cs:wordpress', function(data) {
3627+ var command = {
3628+ op: 'expose',
3629+ service_name: 'foobar'
3630+ };
3631+ state.nextChanges();
3632+ client.onmessage = function() {
3633+ client.onmessage = function(rec) {
3634+ var data = Y.JSON.parse(rec.data);
3635+ assert.equal(data.result, false);
3636+ assert.equal(data.err,
3637+ '"foobar" is an invalid service name.');
3638+ done();
3639+ };
3640+ client.send(Y.JSON.stringify(command));
3641+ };
3642+ client.open();
3643+ }, { unitCount: 1 });
3644+ }
3645+ );
3646+
3647+ it('can unexpose a service', function(done) {
3648+ function unexposeService(rec) {
3649+ var data = Y.JSON.parse(rec.data),
3650+ command = {
3651+ op: 'unexpose',
3652+ service_name: data.service_name
3653+ };
3654+ state.nextChanges();
3655+ client.onmessage = function(rec) {
3656+ var data = Y.JSON.parse(rec.data),
3657+ service = state.db.services.getById(data.service_name),
3658+ mock = {
3659+ op: 'unexpose',
3660+ result: true,
3661+ service_name: 'wordpress'
3662+ };
3663+ assert.equal(service.get('exposed'), false);
3664+ assert.deepEqual(data, mock);
3665+ done();
3666+ };
3667+ client.send(Y.JSON.stringify(command));
3668+ }
3669+ generateAndExposeService(unexposeService);
3670+ });
3671+
3672+ it('can unexpose a service (integration)', function(done) {
3673+ function unexposeService(rec) {
3674+ function localCb(rec) {
3675+ var service = state.db.services.getById('kumquat');
3676+ assert.equal(service.get('exposed'), false);
3677+ assert.equal(rec.result, true);
3678+ done();
3679+ }
3680+ env.unexpose(rec.service_name, localCb);
3681+ }
3682+ generateAndExposeIntegrationService(unexposeService);
3683+ });
3684+
3685+ it('fails silently when unexposing a not exposed service',
3686+ function(done) {
3687+ state.deploy('cs:wordpress', function(data) {
3688+ var command = {
3689+ op: 'unexpose',
3690+ service_name: data.service.get('name')
3691+ };
3692+ state.nextChanges();
3693+ client.onmessage = function() {
3694+ client.onmessage = function(rec) {
3695+ var data = Y.JSON.parse(rec.data),
3696+ service = state.db.services.getById(data.service_name);
3697+ assert.equal(service.get('exposed'), false);
3698+ assert.equal(data.result, true);
3699+ assert.equal(data.err, undefined);
3700+ done();
3701+ };
3702+ client.send(Y.JSON.stringify(command));
3703+ };
3704+ client.open();
3705+ }, { unitCount: 1 });
3706+ }
3707+ );
3708+
3709+ it('fails with error when unexposing an invalid service name',
3710+ function(done) {
3711+ function unexposeService(rec) {
3712+ var data = Y.JSON.parse(rec.data),
3713+ command = {
3714+ op: 'unexpose',
3715+ service_name: 'foobar'
3716+ };
3717+ state.nextChanges();
3718+ client.onmessage = function(rec) {
3719+ var data = Y.JSON.parse(rec.data);
3720+ assert.equal(data.result, false);
3721+ assert.equal(data.err, '"foobar" is an invalid service name.');
3722+ done();
3723+ };
3724+ client.send(Y.JSON.stringify(command));
3725+ }
3726+ generateAndExposeService(unexposeService);
3727+ }
3728+ );
3729+
3730+ it('can add a relation', function(done) {
3731+ function localCb() {
3732+ state.deploy('cs:mysql', function(service) {
3733+ var data = {
3734+ op: 'add_relation',
3735+ endpoint_a: 'wordpress:db',
3736+ endpoint_b: 'mysql:db'
3737+ };
3738+ client.onmessage = function(rec) {
3739+ var data = Y.JSON.parse(rec.data),
3740+ mock = {
3741+ endpoint_a: 'wordpress:db',
3742+ endpoint_b: 'mysql:db',
3743+ op: 'add_relation',
3744+ result: {
3745+ id: 'relation-0',
3746+ 'interface': 'mysql',
3747+ scope: 'global',
3748+ endpoints: [
3749+ {wordpress: {name: 'db'}},
3750+ {mysql: {name: 'db'}}
3751+ ]
3752+ }
3753+ };
3754+
3755+ assert.equal(data.err, undefined);
3756+ assert.equal(typeof data.result, 'object');
3757+ assert.deepEqual(data, mock);
3758+ done();
3759+ };
3760+ client.send(Y.JSON.stringify(data));
3761+ });
3762+ }
3763+ generateServices(localCb);
3764+ });
3765+
3766+ it('can add a relation (integration)', function(done) {
3767+ function addRelation() {
3768+ function localCb(rec) {
3769+ var mock = {
3770+ endpoint_a: 'kumquat:db',
3771+ endpoint_b: 'mysql:db',
3772+ op: 'add_relation',
3773+ request_id: rec.request_id,
3774+ result: {
3775+ id: 'relation-0',
3776+ 'interface': 'mysql',
3777+ scope: 'global',
3778+ request_id: rec.request_id,
3779+ endpoints: [
3780+ {kumquat: {name: 'db'}},
3781+ {mysql: {name: 'db'}}
3782+ ]
3783+ }
3784+ };
3785+
3786+ assert.equal(rec.err, undefined);
3787+ assert.equal(typeof rec.result, 'object');
3788+ assert.deepEqual(rec.details[0], mock);
3789+ done();
3790+ }
3791+ var endpointA = [
3792+ 'kumquat',
3793+ { name: 'db',
3794+ role: 'client' }
3795+ ];
3796+ var endpointB = [
3797+ 'mysql',
3798+ { name: 'db',
3799+ role: 'server' }
3800+ ];
3801+ env.add_relation(endpointA, endpointB, localCb);
3802+ }
3803+ generateIntegrationServices(function() {
3804+ env.deploy('cs:mysql', undefined, undefined, undefined, 1, addRelation);
3805+ });
3806+ });
3807+
3808+ it('is able to add a relation with a subordinate service', function(done) {
3809+ function localCb() {
3810+ state.deploy('cs:puppet', function(service) {
3811+ var data = {
3812+ op: 'add_relation',
3813+ endpoint_a: 'wordpress:juju-info',
3814+ endpoint_b: 'puppet:juju-info'
3815+ };
3816+
3817+ client.onmessage = function(rec) {
3818+ var data = Y.JSON.parse(rec.data),
3819+ mock = {
3820+ endpoint_a: 'wordpress:juju-info',
3821+ endpoint_b: 'puppet:juju-info',
3822+ op: 'add_relation',
3823+ result: {
3824+ id: 'relation-0',
3825+ 'interface': 'juju-info',
3826+ scope: 'container',
3827+ endpoints: [
3828+ {puppet: {name: 'juju-info'}},
3829+ {wordpress: {name: 'juju-info'}}
3830+ ]
3831+ }
3832+ };
3833+ assert.equal(data.err, undefined);
3834+ assert.equal(typeof data.result, 'object');
3835+ assert.deepEqual(data, mock);
3836+ done();
3837+ };
3838+ client.send(Y.JSON.stringify(data));
3839+ });
3840+ }
3841+ generateServices(localCb);
3842+ });
3843+
3844+ it('throws an error if only one endpoint is supplied', function(done) {
3845+ function localCb() {
3846+ var data = {
3847+ op: 'add_relation',
3848+ endpoint_a: 'wordpress:db'
3849+ };
3850+ state.nextChanges();
3851+ client.onmessage = function(rec) {
3852+ var data = Y.JSON.parse(rec.data);
3853+ assert(data.err, 'Two endpoints required to set up relation.');
3854+ done();
3855+ };
3856+ client.send(Y.JSON.stringify(data));
3857+ }
3858+ generateServices(localCb);
3859+ });
3860+
3861+ it('throws an error if endpoints are not relatable', function(done) {
3862+ function localCb() {
3863+ var data = {
3864+ op: 'add_relation',
3865+ endpoint_a: 'wordpress:db',
3866+ endpoint_b: 'mysql:foo'
3867+ };
3868+ state.nextChanges();
3869+ client.onmessage = function(rec) {
3870+ var data = Y.JSON.parse(rec.data);
3871+ assert(data.err, 'No matching interfaces.');
3872+ done();
3873+ };
3874+ client.send(Y.JSON.stringify(data));
3875+ }
3876+ generateServices(localCb);
3877+ });
3878+
3879+ it('can remove a relation', function(done) {
3880+ generateAndRelateServices(
3881+ ['cs:wordpress', 'cs:mysql'],
3882+ ['wordpress:db', 'mysql:db'],
3883+ ['wordpress:db', 'mysql:db'],
3884+ {result: true, endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
3885+ done);
3886+ });
3887+
3888+ it('can remove a relation (integration)', function(done) {
3889+ var endpoints = [
3890+ ['kumquat',
3891+ { name: 'db',
3892+ role: 'client' }],
3893+ ['mysql',
3894+ { name: 'db',
3895+ role: 'server' }]
3896+ ];
3897+ env.after('defaultSeriesChange', function() {
3898+ function localCb(result) {
3899+ var mock = {
3900+ endpoint_a: 'kumquat:db',
3901+ endpoint_b: 'mysql:db',
3902+ op: 'remove_relation',
3903+ request_id: 4,
3904+ result: true
3905+ };
3906+ assert.deepEqual(result.details[0], mock);
3907+ done();
3908+ }
3909+ env.deploy(
3910+ 'cs:wordpress', 'kumquat', {llama: 'pajama'}, null, 1, function() {
3911+ env.deploy('cs:mysql', null, null, null, 1, function() {
3912+ env.add_relation(endpoints[0], endpoints[1], function() {
3913+ env.remove_relation(endpoints[0], endpoints[1], localCb);
3914+ });
3915+ });
3916+ }
3917+ );
3918+ });
3919+ env.connect();
3920+ });
3921+
3922+ it('throws an error if the charms do not exist', function(done) {
3923+ generateAndRelateServices(
3924+ ['cs:wordpress', 'cs:mysql'],
3925+ ['wordpress:db', 'mysql:db'],
3926+ ['no_such', 'charms'],
3927+ {err: 'Charm not loaded.',
3928+ endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
3929+ done);
3930+ });
3931+
3932+ it('throws an error if the relationship does not exist', function(done) {
3933+ generateAndRelateServices(
3934+ ['cs:wordpress', 'cs:mysql'],
3935+ null,
3936+ ['wordpress:db', 'mysql:db'],
3937+ {err: 'Relationship does not exist',
3938+ endpoint_a: 'wordpress:db', endpoint_b: 'mysql:db'},
3939+ done);
3940+ });
3941+
3942+ describe('Sandbox Annotations', function() {
3943+
3944+ it('should handle service annotation updates', function(done) {
3945+ generateServices(function(data) {
3946+ // Post deploy of wordpress we should be able to
3947+ // pull its data.
3948+ var op = {
3949+ op: 'update_annotations',
3950+ entity: 'wordpress',
3951+ data: {'foo': 'bar'},
3952+ request_id: 99
3953+ };
3954+ client.onmessage = function(received) {
3955+ var service = state.db.services.getById('wordpress');
3956+ var annotations = service.get('annotations');
3957+ assert.equal(annotations.foo, 'bar');
3958+ // Validate that annotations appear in the delta stream.
3959+ client.onmessage = function(delta) {
3960+ delta = Y.JSON.parse(delta.data);
3961+ assert.equal(delta.op, 'delta');
3962+ var serviceChange = Y.Array.find(delta.result, function(change) {
3963+ return change[0] === 'service';
3964+ });
3965+ assert.equal(serviceChange[0], 'service');
3966+ assert.equal(serviceChange[1], 'change');
3967+ assert.deepEqual(serviceChange[2].annotations, {'foo': 'bar'});
3968+ // Error should be undefined.
3969+ done(received.error);
3970+ };
3971+ juju.sendDelta();
3972+ };
3973+ client.open();
3974+ client.send(Y.JSON.stringify(op));
3975+ });
3976+ });
3977+
3978+ it('should handle environment annotation updates', function(done) {
3979+ generateServices(function(data) {
3980+ // We only deploy a service here to reuse the env connect/setup
3981+ // code.
3982+ // Post deploy of wordpress we should be able to
3983+ // pull env data.
3984+ client.onmessage = function(received) {
3985+ var env = state.db.environment;
3986+ var annotations = env.get('annotations');
3987+ assert.equal(annotations.foo, 'bar');
3988+ // Validate that annotations appear in the delta stream.
3989+ client.onmessage = function(delta) {
3990+ delta = Y.JSON.parse(delta.data);
3991+ assert.equal(delta.op, 'delta');
3992+ var envChange = Y.Array.find(delta.result, function(change) {
3993+ return change[0] === 'annotations';
3994+ });
3995+ assert.equal(envChange[1], 'change');
3996+ assert.deepEqual(envChange[2], {'foo': 'bar'});
3997+ done();
3998+ };
3999+ juju.sendDelta();
4000+ };
4001+ client.open();
4002+ client.send(Y.JSON.stringify({
4003+ op: 'update_annotations',
4004+ entity: 'env',
4005+ data: {'foo': 'bar'},
4006+ request_id: 99
4007+ }));
4008+ });
4009+ });
4010+
4011+ it('should handle unit annotation updates', function(done) {
4012+ generateServices(function(data) {
4013+ // Post deploy of wordpress we should be able to
4014+ // pull its data.
4015+ var op = {
4016+ op: 'update_annotations',
4017+ entity: 'wordpress/0',
4018+ data: {'foo': 'bar'},
4019+ request_id: 99
4020+ };
4021+ client.onmessage = function(received) {
4022+ var unit = state.db.units.getById('wordpress/0');
4023+ var annotations = unit.annotations;
4024+ assert.equal(annotations.foo, 'bar');
4025+ // Error should be undefined.
4026+ done(received.error);
4027+ };
4028+ client.open();
4029+ client.send(Y.JSON.stringify(op));
4030+ });
4031+ });
4032+
4033+ });
4034+
4035+ it('should allow unit resolved to be called', function(done) {
4036+ generateServices(function(data) {
4037+ // Post deploy of wordpress we should be able to
4038+ // pull its data.
4039+ var op = {
4040+ op: 'resolved',
4041+ unit_name: 'wordpress/0',
4042+ request_id: 99
4043+ };
4044+ client.onmessage = function(received) {
4045+ var parsed = Y.JSON.parse(received.data);
4046+ assert.equal(parsed.result, true);
4047+ done(parsed.error);
4048+ };
4049+ client.open();
4050+ client.send(Y.JSON.stringify(op));
4051+ });
4052+ });
4053+
4054+ /**
4055+ * Utility method to turn _some_ callback
4056+ * styled async methods into Promises.
4057+ * It does this by supplying a simple
4058+ * adaptor that can handle {error:...}
4059+ * and {result: ... } returns.
4060+ *
4061+ * This callback is appended to any calling arguments
4062+ *
4063+ * @method promise
4064+ * @param {Object} context Calling context.
4065+ * @param {String} methodName name of method on context to invoke.
4066+ * @param {Arguments} arguments Additional arguments passed
4067+ * to resolved method.
4068+ * @return {Promise} a Y.Promise object.
4069+ */
4070+ function promise(context, methodName) {
4071+ var slice = Array.prototype.slice;
4072+ var args = slice.call(arguments, 2);
4073+ var method = context[methodName];
4074+
4075+ return Y.Promise(function(resolve, reject) {
4076+ var resultHandler = function(result) {
4077+ if (result.err || result.error) {
4078+ reject(result.err || result.error);
4079+ } else {
4080+ resolve(result);
4081+ }
4082+ };
4083+
4084+ args.push(resultHandler);
4085+ var result = method.apply(context, args);
4086+ if (result !== undefined) {
4087+ // The method returned right away.
4088+ return resultHandler(result);
4089+ }
4090+ });
4091+ }
4092+
4093+ it('should support export', function(done) {
4094+ client.open();
4095+ promise(state, 'deploy', 'cs:wordpress')
4096+ .then(promise(state, 'deploy', 'cs:mysql'))
4097+ .then(promise(state, 'addRelation', 'wordpress:db', 'mysql:db'))
4098+ .then(function() {
4099+ client.onmessage = function(result) {
4100+ var data = Y.JSON.parse(result.data).result;
4101+ assert.equal(data.services[0].name, 'wordpress');
4102+ done();
4103+ };
4104+ client.send(Y.JSON.stringify({op: 'exportEnvironment'}));
4105+ });
4106+ });
4107+
4108+ it('should support import', function(done) {
4109+ var fixture = utils.loadFixture('data/sample-fakebackend.json', false);
4110+
4111+ client.onmessage = function() {
4112+ client.onmessage = function(result) {
4113+ var data = Y.JSON.parse(result.data).result;
4114+ assert.isTrue(data);
4115+
4116+ // Verify that we can now find an expected entry
4117+ // in the database.
4118+ assert.isNotNull(state.db.services.getById('wordpress'));
4119+
4120+ var changes = state.nextChanges();
4121+ // Validate the delta includes imported services.
4122+ assert.include(changes.services, 'wordpress');
4123+ assert.include(changes.services, 'mysql');
4124+ // validate relation was added/updated.
4125+ assert.include(changes.relations, 'relation-0');
4126+ done();
4127+ };
4128+ client.send(Y.JSON.stringify({op: 'importEnvironment',
4129+ envData: fixture}));
4130+ };
4131+ client.open();
4132+ });
4133+
4134+ });
4135+
4136+})();

Subscribers

People subscribed via source and target branches