Merge lp:~gary/juju-gui/sandboxEnv-3 into lp:juju-gui/experimental

Proposed by Gary Poster
Status: Merged
Merged at revision: 443
Proposed branch: lp:~gary/juju-gui/sandboxEnv-3
Merge into: lp:juju-gui/experimental
Diff against target: 744 lines (+618/-21)
9 files modified
app/modules-debug.js (+4/-0)
app/store/env/base.js (+7/-1)
app/store/env/fakebackend.js (+10/-2)
app/store/env/sandbox.js (+259/-0)
test/index.html (+1/-0)
test/test_app.js (+1/-0)
test/test_env.js (+26/-0)
test/test_fakebackend.js (+2/-18)
test/test_sandbox.js (+308/-0)
To merge this branch: bzr merge lp:~gary/juju-gui/sandboxEnv-3
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+154403@code.launchpad.net

Description of the change

Add remaining incomplete components of sandbox

This adds a connection and Python juju API implementation of the in-browser-memory environment. The final test shows a complete integration of login from environment through to the fake backend.

https://codereview.appspot.com/7621048/

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Reviewers: mp+154403_code.launchpad.net,

Message:
Please take a look.

Description:
Add remaining incomplete components of sandbox

This adds a connection and Python juju API implementation of the
in-browser-memory environment. The final test shows a complete
integration of login from environment through to the fake backend.

This is from a spike, and I have a bit more to translate from the spike
in a follow-on branch. The follow-on branch will support deploying to
the in-memory state from the environment, and from the GUI itself. I
decided to propose this first since the branch size was getting a bit
large and it was reasonably self-sufficient as it was.

https://code.launchpad.net/~gary/juju-gui/sandboxEnv-3/+merge/154403

(do not edit description out of merge proposal)

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

Affected files:
   A [revision details]
   M app/modules-debug.js
   M app/store/env/base.js
   M app/store/env/fakebackend.js
   A app/store/env/sandbox.js
   M test/index.html
   M test/test_app.js
   M test/test_env.js
   M test/test_fakebackend.js
   A test/test_sandbox.js

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

LGTM Thanks for the branch!

https://codereview.appspot.com/7621048/

Revision history for this message
Madison Scott-Clary (makyo) wrote :

LGTM, thanks!

Few minor points that I don't really care about, just questions. Feel
free to ignore.

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js
File app/store/env/base.js (right):

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js#newcode67
app/store/env/base.js:67: } // else we never connected.
Super minor: we haven't really been doing much in the way of inline
comments in the past. I'm pretty sure this is meant to be a fake 'else'
statement, but maybe it could go above the if:

// Close the socket, but only if we have connected.

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js#newcode84
app/store/env/base.js:84: this.ws.open(); // For the fake backends.
Super minor: inline comment as above.

https://codereview.appspot.com/7621048/

Revision history for this message
Gary Poster (gary) wrote :

*** Submitted:

Add remaining incomplete components of sandbox

This adds a connection and Python juju API implementation of the
in-browser-memory environment. The final test shows a complete
integration of login from environment through to the fake backend.

R=jeff.pihach, matthew.scott
CC=
https://codereview.appspot.com/7621048

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js
File app/store/env/base.js (right):

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js#newcode67
app/store/env/base.js:67: } // else we never connected.
On 2013/03/20 15:53:39, matthew.scott wrote:
> Super minor: we haven't really been doing much in the way of inline
comments in
> the past. I'm pretty sure this is meant to be a fake 'else'
statement, but
> maybe it could go above the if:

> // Close the socket, but only if we have connected.

Done.

https://codereview.appspot.com/7621048/diff/1/app/store/env/base.js#newcode84
app/store/env/base.js:84: this.ws.open(); // For the fake backends.
On 2013/03/20 15:53:39, matthew.scott wrote:
> Super minor: inline comment as above.

Done.

https://codereview.appspot.com/7621048/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/modules-debug.js'
2--- app/modules-debug.js 2013-03-13 22:43:40 +0000
3+++ app/modules-debug.js 2013-03-20 15:20:29 +0000
4@@ -216,6 +216,10 @@
5 fullpath: '/juju-ui/store/env/fakebackend.js'
6 },
7
8+ 'juju-env-sandbox': {
9+ fullpath: '/juju-ui/store/env/sandbox.js'
10+ },
11+
12 'juju-notification-controller': {
13 fullpath: '/juju-ui/store/notifications.js'
14 },
15
16=== modified file 'app/store/env/base.js'
17--- app/store/env/base.js 2013-02-21 18:07:54 +0000
18+++ app/store/env/base.js 2013-03-20 15:20:29 +0000
19@@ -62,7 +62,9 @@
20 },
21
22 destructor: function() {
23- this.ws.close();
24+ if (this.ws) {
25+ this.ws.close();
26+ } // else we never connected.
27 this._txn_callbacks = {};
28 },
29
30@@ -78,6 +80,9 @@
31 this.ws.onmessage = Y.bind(this.on_message, this);
32 this.ws.onopen = Y.bind(this.on_open, this);
33 this.ws.onclose = Y.bind(this.on_close, this);
34+ if (this.ws.open) {
35+ this.ws.open(); // For the fake backends.
36+ }
37 return this;
38 },
39
40@@ -192,6 +197,7 @@
41 requires: [
42 'base',
43 'json-parse',
44+ 'json-stringify',
45 'reconnecting-websocket'
46 ]
47 });
48
49=== modified file 'app/store/env/fakebackend.js'
50--- app/store/env/fakebackend.js 2013-03-12 13:34:26 +0000
51+++ app/store/env/fakebackend.js 2013-03-20 15:20:29 +0000
52@@ -75,8 +75,16 @@
53 if (!this.get('authenticated')) {
54 return UNAUTHENTICATEDERROR;
55 }
56- var result = this.changes;
57- this._resetChanges();
58+ var result;
59+ if (Y.Object.isEmpty(this.changes.services) &&
60+ Y.Object.isEmpty(this.changes.machines) &&
61+ Y.Object.isEmpty(this.changes.units) &&
62+ Y.Object.isEmpty(this.changes.relations)) {
63+ result = null;
64+ } else {
65+ result = this.changes;
66+ this._resetChanges();
67+ }
68 return result;
69 },
70
71
72=== added file 'app/store/env/sandbox.js'
73--- app/store/env/sandbox.js 1970-01-01 00:00:00 +0000
74+++ app/store/env/sandbox.js 2013-03-20 15:20:29 +0000
75@@ -0,0 +1,259 @@
76+'use strict';
77+
78+/**
79+ Sandbox APIs mimicking communications with the Go and Juju backends.
80+
81+ @module env
82+ @submodule env.sandbox
83+ */
84+
85+YUI.add('juju-env-sandbox', function(Y) {
86+
87+ var sandboxModule = Y.namespace('juju.environments.sandbox');
88+ var CLOSEDERROR = 'INVALID_STATE_ERR : Connection is closed.';
89+
90+ /**
91+ * A client connection for interacting with a sandbox environment.
92+ *
93+ * @class ClientConnection
94+ */
95+ function ClientConnection(config) {
96+ ClientConnection.superclass.constructor.apply(this, arguments);
97+ }
98+
99+ ClientConnection.NAME = 'sandbox-client-connection';
100+ ClientConnection.ATTRS = {
101+ juju: {} // Required.
102+ };
103+
104+ Y.extend(ClientConnection, Y.Base, {
105+
106+ /**
107+ Initialize.
108+
109+ @method initializer
110+ @return {undefined} Nothing.
111+ */
112+ initializer: function() {
113+ this.connected = false;
114+ },
115+
116+ /**
117+ React to a new message from Juju.
118+ You are expected to monkeypatch this method, as with websockets.
119+
120+ @method onmessage
121+ @param {Object} event An object with a JSON string on the "data"
122+ attribute.
123+ @return {undefined} Nothing.
124+ */
125+ onmessage: function(event) {},
126+
127+ /**
128+ Immediately give message to listener (contrast with receive).
129+ Uses onmessage to deliver message, as with websockets.
130+
131+ @method receiveNow
132+ @param {Object} data An object to be sent as JSON to the listener.
133+ @return {undefined} Nothing.
134+ */
135+ receiveNow: function(data) {
136+ if (this.connected) {
137+ this.onmessage({data: Y.JSON.stringify(data)});
138+ } else {
139+ throw CLOSEDERROR;
140+ }
141+ },
142+
143+ /**
144+ Give message to listener asynchronously (contrast with receiveNow).
145+ Uses onmessage to deliver message, as with websockets.
146+
147+ @method receive
148+ @param {Object} data An object to be sent as JSON to the listener.
149+ @return {undefined} Nothing.
150+ */
151+ receive: function(data) {
152+ if (this.connected) {
153+ // 4 milliseconds is the smallest effective time available to wait. See
154+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers
155+ setTimeout(this.receiveNow.bind(this, data), 4);
156+ } else {
157+ throw CLOSEDERROR;
158+ }
159+ },
160+
161+ /**
162+ Send a JSON string to the API.
163+
164+ @method send
165+ @param {String} data A JSON string of the data to be sent.
166+ @return {undefined} Nothing.
167+ */
168+ send: function(data) {
169+ if (this.connected) {
170+ this.get('juju').receive(Y.JSON.parse(data));
171+ } else {
172+ throw CLOSEDERROR;
173+ }
174+ },
175+
176+ /**
177+ React to an opening connection.
178+ You are expected to monkeypatch this method, as with websockets.
179+
180+ @method onopen
181+ @return {undefined} Nothing.
182+ */
183+ onopen: function() {},
184+
185+ /**
186+ Explicitly open the connection.
187+ This does not have an analog with websockets, but requiring an explicit
188+ "open" means less magic is necessary. It is responsible for changing
189+ the "connected" state, for calling the onopen hook, and for calling
190+ the sandbox juju.open with itself.
191+
192+ @method open
193+ @return {undefined} Nothing.
194+ */
195+ open: function() {
196+ if (!this.connected) {
197+ this.connected = true;
198+ this.get('juju').open(this);
199+ this.onopen();
200+ }
201+ },
202+
203+ /**
204+ React to a closing connection.
205+ You are expected to monkeypatch this method, as with websockets.
206+
207+ @method onclose
208+ @return {undefined} Nothing.
209+ */
210+ onclose: function() {},
211+
212+ /**
213+ Close the connection.
214+ This is responsible for changing the "connected" state, for calling the
215+ onclosed hook, and for calling the sandbox juju.close.
216+
217+ @method close
218+ @return {undefined} Nothing.
219+ */
220+ close: function() {
221+ if (this.connected) {
222+ this.connected = false;
223+ this.get('juju').close();
224+ this.onclose();
225+ }
226+ }
227+
228+ });
229+
230+ sandboxModule.ClientConnection = ClientConnection;
231+
232+ /**
233+ * A sandbox Juju environment using the Python API.
234+ *
235+ * @class PyJujuAPI
236+ */
237+ function PyJujuAPI(config) {
238+ PyJujuAPI.superclass.constructor.apply(this, arguments);
239+ }
240+
241+ PyJujuAPI.NAME = 'sandbox-py-juju-api';
242+ PyJujuAPI.ATTRS = {
243+ state: {}, // Required.
244+ client: {} // Set in the "open" method.
245+ };
246+
247+ Y.extend(PyJujuAPI, Y.Base, {
248+
249+ /**
250+ Initializes.
251+
252+ @method initializer
253+ @return {undefined} Nothing.
254+ */
255+ initializer: function() {
256+ this.connected = false;
257+ },
258+
259+ /**
260+ Opens the connection to the sandbox Juju environment.
261+ Called by ClientConnection, which sends itself.
262+
263+ @method open
264+ @param {Object} client A ClientConnection.
265+ @return {undefined} Nothing.
266+ */
267+ open: function(client) {
268+ if (!this.connected) {
269+ this.connected = true;
270+ this.set('client', client);
271+ var state = this.get('state');
272+ client.receive({
273+ ready: true,
274+ provider_type: state.get('providerType'),
275+ default_series: state.get('defaultSeries')
276+ });
277+ } else if (this.get('client') !== client) {
278+ throw 'INVALID_STATE_ERR : Connection is open to another client.';
279+ }
280+ },
281+
282+ /**
283+ Closes the connection to the sandbox Juju environment.
284+ Called by ClientConnection.
285+
286+ @method close
287+ @return {undefined} Nothing.
288+ */
289+ close: function() {
290+ this.connected = false;
291+ this.set('client', undefined);
292+ },
293+
294+ /**
295+ Receives messages from the client and dispatches them.
296+
297+ @method receive
298+ @param {Object} data A hash of data sent from the client.
299+ @return {undefined} Nothing.
300+ */
301+ receive: function(data) {
302+ // Make a shallow copy of the received data because handlers will mutate
303+ // it to add an "err" or "result".
304+ if (this.connected) {
305+ this['performOp_' + data.op](Y.merge(data));
306+ } else {
307+ throw CLOSEDERROR;
308+ }
309+ },
310+
311+ /**
312+ Handles login operations from the client. Called by "receive".
313+ client.receive will receive all sent values back, transparently,
314+ plus a "result" value that will be true or false, representing whether
315+ the authentication succeeded or failed.
316+
317+ @method performOp_login
318+ @param {Object} data A hash minimally of user and password.
319+ @return {undfefined} Nothing.
320+ */
321+ performOp_login: function(data) {
322+ data.result = this.get('state').login(data.user, data.password);
323+ this.get('client').receive(data);
324+ }
325+ });
326+
327+ sandboxModule.PyJujuAPI = PyJujuAPI;
328+
329+}, '0.1.0', {
330+ requires: [
331+ 'base',
332+ 'json-parse'
333+ ]
334+});
335
336=== modified file 'test/index.html'
337--- test/index.html 2013-03-13 21:13:05 +0000
338+++ test/index.html 2013-03-20 15:20:29 +0000
339@@ -59,6 +59,7 @@
340 <script src="test_panzoom.js"></script>
341 <script src="test_prettify.js"></script>
342 <script src="test_routing.js"></script>
343+ <script src="test_sandbox.js"></script>
344 <script src="test_service_config_view.js"></script>
345 <script src="test_service_module.js"></script>
346 <script src="test_service_view.js"></script>
347
348=== modified file 'test/test_app.js'
349--- test/test_app.js 2013-03-13 17:59:29 +0000
350+++ test/test_app.js 2013-03-20 15:20:29 +0000
351@@ -191,6 +191,7 @@
352
353 it('should avoid trying to login if the env is not connected',
354 function(done) {
355+ conn.transient_close();
356 var app = new Y.juju.App({env: env});
357 app.after('ready', function() {
358 assert.equal(0, conn.messages.length);
359
360=== modified file 'test/test_env.js'
361--- test/test_env.js 2013-02-15 12:29:14 +0000
362+++ test/test_env.js 2013-03-20 15:20:29 +0000
363@@ -41,4 +41,30 @@
364
365 });
366
367+ describe('Base Environment', function() {
368+ var requires = ['juju-env-base', 'juju-env-sandbox'];
369+ var environments, juju, Y, sandboxModule, ClientConnection;
370+
371+ before(function(done) {
372+ Y = YUI(GlobalConfig).use(requires, function(Y) {
373+ juju = Y.namespace('juju');
374+ environments = juju.environments;
375+ sandboxModule = Y.namespace('juju.environments.sandbox');
376+ ClientConnection = sandboxModule.ClientConnection;
377+ done();
378+ });
379+ });
380+
381+ it('calls "open" on connection if available.', function() {
382+ var conn = new ClientConnection({juju: {open: function() {}}});
383+ var env = new environments.BaseEnvironment({conn: conn});
384+ assert.isFalse(conn.connected);
385+ assert.isFalse(env.get('connected'));
386+ env.connect();
387+ assert.isTrue(conn.connected);
388+ assert.isTrue(env.get('connected'));
389+ });
390+
391+ });
392+
393 })();
394
395=== modified file 'test/test_fakebackend.js'
396--- test/test_fakebackend.js 2013-03-19 14:14:15 +0000
397+++ test/test_fakebackend.js 2013-03-20 15:20:29 +0000
398@@ -369,15 +369,7 @@
399 });
400
401 it('reports no changes initially.', function() {
402- assert.deepEqual(
403- fakebackend.nextChanges(),
404- {
405- services: {},
406- machines: {},
407- units: {},
408- relations: {}
409- }
410- );
411+ assert.isNull(fakebackend.nextChanges());
412 });
413
414 it('reports a call to addUnit correctly.', function() {
415@@ -430,15 +422,7 @@
416 fakebackend.deploy('cs:wordpress', callback);
417 assert.isUndefined(deployResult.error);
418 assert.isObject(fakebackend.nextChanges());
419- assert.deepEqual(
420- fakebackend.nextChanges(),
421- {
422- services: {},
423- machines: {},
424- units: {},
425- relations: {}
426- }
427- );
428+ assert.isNull(fakebackend.nextChanges());
429 }
430 );
431
432
433=== added file 'test/test_sandbox.js'
434--- test/test_sandbox.js 1970-01-01 00:00:00 +0000
435+++ test/test_sandbox.js 2013-03-20 15:20:29 +0000
436@@ -0,0 +1,308 @@
437+'use strict';
438+
439+(function() {
440+
441+ describe('sandbox.ClientConnection', function() {
442+ var requires = ['juju-env-sandbox', 'json-stringify'];
443+ var Y, sandboxModule, ClientConnection;
444+
445+ before(function(done) {
446+ Y = YUI(GlobalConfig).use(requires, function(Y) {
447+ sandboxModule = Y.namespace('juju.environments.sandbox');
448+ ClientConnection = sandboxModule.ClientConnection;
449+ done();
450+ });
451+ });
452+
453+ it('opens successfully in isolation.', function() {
454+ var receivedFromOpen;
455+ var jujuopen = function(client) {
456+ receivedFromOpen = client;
457+ };
458+ var conn = new ClientConnection({juju: {open: jujuopen}});
459+ var onopenFlag = false;
460+ conn.onopen = function() {
461+ onopenFlag = true;
462+ };
463+ assert.isFalse(conn.connected);
464+ conn.open();
465+ assert.isTrue(conn.connected);
466+ assert.isTrue(onopenFlag);
467+ assert.strictEqual(receivedFromOpen, conn);
468+ });
469+
470+ it('silently ignores requests to open when already open.', function() {
471+ // This is the preparation.
472+ var jujuopenFlag = false;
473+ var jujuopen = function() {
474+ jujuopenFlag = true;
475+ };
476+ var conn = new ClientConnection({juju: {open: jujuopen}});
477+ assert.isFalse(conn.connected);
478+ conn.open();
479+ jujuopenFlag = false;
480+ var onopenFlag = false;
481+ conn.onopen = function() {
482+ onopenFlag = true;
483+ };
484+ // This is the test.
485+ conn.open();
486+ assert.isTrue(conn.connected);
487+ assert.isFalse(onopenFlag);
488+ assert.isFalse(jujuopenFlag);
489+ });
490+
491+ it('closes successfully in isolation.', function() {
492+ var jujuclosedFlag;
493+ var jujuclosed = function() {
494+ jujuclosedFlag = true;
495+ };
496+ var conn = new ClientConnection({
497+ juju: {
498+ open: function() {},
499+ close: jujuclosed
500+ }
501+ });
502+ conn.open();
503+ assert.isTrue(conn.connected);
504+ var oncloseFlag = false;
505+ conn.onclose = function() {
506+ oncloseFlag = true;
507+ };
508+ conn.close();
509+ assert.isFalse(conn.connected);
510+ assert.isTrue(oncloseFlag);
511+ assert.isTrue(jujuclosedFlag);
512+ });
513+
514+ it('silently ignores requests to close when already closed', function() {
515+ var jujuclosedFlag = false;
516+ var jujuclosed = function() {
517+ jujuclosedFlag = true;
518+ };
519+ var conn = new ClientConnection({
520+ juju: {
521+ open: function() {},
522+ close: jujuclosed
523+ }
524+ });
525+ assert.isFalse(conn.connected);
526+ var oncloseFlag = false;
527+ conn.onclose = function() {
528+ oncloseFlag = true;
529+ };
530+ conn.close();
531+ assert.isFalse(conn.connected);
532+ assert.isFalse(oncloseFlag);
533+ assert.isFalse(jujuclosedFlag);
534+ });
535+
536+ it('sends messages to the API.', function() {
537+ var received;
538+ var sent = {response: 42, foo: ['bar', 'shazam']};
539+ var conn = new ClientConnection({
540+ juju: {
541+ open: function() {},
542+ receive: function(data) {received = data;}
543+ }
544+ });
545+ conn.open();
546+ conn.send(Y.JSON.stringify(sent));
547+ assert.deepEqual(received, sent);
548+ });
549+
550+ it('can receive messages from the API immediately.', function() {
551+ var data = {sample: 'foo', bar: [42, 36]};
552+ var conn = new ClientConnection({juju: {open: function() {}}});
553+ var received;
554+ conn.onmessage = function(event) {received = event;};
555+ conn.open();
556+ conn.receiveNow(data);
557+ assert.isString(received.data);
558+ assert.deepEqual(Y.JSON.parse(received.data), data);
559+ });
560+
561+ it('receives messages from the API asynchronously.', function(done) {
562+ var data = {sample: 'foo', bar: [42, 36]};
563+ var conn = new ClientConnection({juju: {open: function() {}}});
564+ var isAsync = false;
565+ conn.onmessage = function(received) {
566+ assert.isString(received.data);
567+ assert.deepEqual(Y.JSON.parse(received.data), data);
568+ assert.isTrue(isAsync);
569+ done();
570+ };
571+ conn.open();
572+ conn.receive(data);
573+ isAsync = true;
574+ });
575+
576+ it('refuses to send messages when not connected.', function() {
577+ var conn = new ClientConnection({juju: {open: function() {}}});
578+ assert.throws(
579+ conn.send.bind(conn, {response: 42}),
580+ 'INVALID_STATE_ERR : Connection is closed.');
581+ });
582+
583+ it('refuses to receive immediately when not connected.', function() {
584+ var conn = new ClientConnection({juju: {open: function() {}}});
585+ assert.throws(
586+ conn.receiveNow.bind(conn, {response: 42}),
587+ 'INVALID_STATE_ERR : Connection is closed.');
588+ });
589+
590+ it('refuses to receive asynchronously when not connected.', function() {
591+ var conn = new ClientConnection({juju: {open: function() {}}});
592+ assert.throws(
593+ conn.receive.bind(conn, {response: 42}),
594+ 'INVALID_STATE_ERR : Connection is closed.');
595+ });
596+
597+ });
598+
599+ describe('sandbox.PyJujuAPI', function() {
600+ var requires = [
601+ 'juju-env-sandbox', 'juju-env-fakebackend', 'juju-env-python'];
602+ var Y, sandboxModule, ClientConnection, PyJujuAPI, environmentsModule,
603+ state, juju, client, env;
604+
605+ before(function(done) {
606+ Y = YUI(GlobalConfig).use(requires, function(Y) {
607+ sandboxModule = Y.namespace('juju.environments.sandbox');
608+ environmentsModule = Y.namespace('juju.environments');
609+ done();
610+ });
611+ });
612+
613+ beforeEach(function() {
614+ state = new environmentsModule.FakeBackend();
615+ juju = new sandboxModule.PyJujuAPI({state: state});
616+ client = new sandboxModule.ClientConnection({juju: juju});
617+ env = new environmentsModule.PythonEnvironment({conn: client});
618+ });
619+
620+ afterEach(function() {
621+ env.destroy();
622+ client.destroy();
623+ juju.destroy();
624+ state.destroy();
625+ });
626+
627+ it('opens successfully.', function(done) {
628+ var isAsync = false;
629+ client.onmessage = function(message) {
630+ assert.isTrue(isAsync);
631+ assert.deepEqual(
632+ Y.JSON.parse(message.data),
633+ {
634+ ready: true,
635+ provider_type: 'demonstration',
636+ default_series: 'precise'
637+ });
638+ done();
639+ };
640+ assert.isFalse(juju.connected);
641+ assert.isUndefined(juju.get('client'));
642+ client.open();
643+ assert.isTrue(juju.connected);
644+ assert.strictEqual(juju.get('client'), client);
645+ isAsync = true;
646+ });
647+
648+ it('ignores "open" when already open to same client.', function() {
649+ client.receive = function() {
650+ assert.fail('The receive method should not be called.');
651+ };
652+ // Whitebox test: duplicate "open" state.
653+ juju.connected = true;
654+ juju.set('client', client);
655+ // This is effectively a re-open.
656+ client.open();
657+ // The assert.fail above is the verification.
658+ });
659+
660+ it('refuses to open if already open to another client.', function() {
661+ // This is a simple way to make sure that we don't leave multiple
662+ // setInterval calls running. If for some reason we want more
663+ // simultaneous clients, that's fine, though that will require
664+ // reworking the delta code generally.
665+ juju.connected = true;
666+ juju.set('client', {receive: assert.fail});
667+ assert.throws(
668+ client.open.bind(client),
669+ 'INVALID_STATE_ERR : Connection is open to another client.');
670+ });
671+
672+ it('closes successfully.', function(done) {
673+ client.onmessage = function() {
674+ client.close();
675+ assert.isFalse(juju.connected);
676+ assert.isUndefined(juju.get('client'));
677+ done();
678+ };
679+ client.open();
680+ });
681+
682+ it('ignores "close" when already closed.', function() {
683+ // This simply shows that we do not raise an error.
684+ juju.close();
685+ });
686+
687+ it('can dispatch on received information.', function(done) {
688+ var data = {op: 'testingTesting123', foo: 'bar'};
689+ juju.performOp_testingTesting123 = function(received) {
690+ assert.notStrictEqual(received, data);
691+ assert.deepEqual(received, data);
692+ done();
693+ };
694+ client.open();
695+ client.send(Y.JSON.stringify(data));
696+ });
697+
698+ it('refuses to dispatch when closed.', function() {
699+ assert.throws(
700+ juju.receive.bind(juju, {}),
701+ 'INVALID_STATE_ERR : Connection is closed.'
702+ );
703+ });
704+
705+ it('can log in.', function(done) {
706+ // See FakeBackend's authorizedUsers for these default authentication
707+ // values.
708+ var data = {
709+ op: 'login',
710+ user: 'admin',
711+ password: 'password',
712+ request_id: 42
713+ };
714+ client.onmessage = function(received) {
715+ // First message is the provider type and default series. We ignore
716+ // it, and prepare for the next one, which will be the reply to our
717+ // login.
718+ client.onmessage = function(received) {
719+ data.result = true;
720+ assert.deepEqual(Y.JSON.parse(received.data), data);
721+ assert.isTrue(state.get('authenticated'));
722+ done();
723+ };
724+ client.send(Y.JSON.stringify(data));
725+ };
726+ client.open();
727+ });
728+
729+ it('can log in (environment integration).', function(done) {
730+ env.after('defaultSeriesChange', function() {
731+ // See FakeBackend's authorizedUsers for these default values.
732+ env.setCredentials({user: 'admin', password: 'password'});
733+ env.after('login', function() {
734+ assert.isTrue(env.userIsAuthenticated);
735+ done();
736+ });
737+ env.login();
738+ });
739+ env.connect();
740+ });
741+
742+ });
743+
744+})();

Subscribers

People subscribed via source and target branches