Merge lp:~gary/juju-gui/sandboxEnv-3 into lp:juju-gui/experimental
- sandboxEnv-3
- Merge into trunk
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+154403@code.launchpad.net |
Commit message
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.
Gary Poster (gary) wrote : | # |
Jeff Pihach (hatch) wrote : | # |
LGTM Thanks for the branch!
Madison Scott-Clary (makyo) wrote : | # |
LGTM, thanks!
Few minor points that I don't really care about, just questions. Feel
free to ignore.
https:/
File app/store/
https:/
app/store/
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:/
app/store/
Super minor: inline comment as above.
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:/
https:/
File app/store/
https:/
app/store/
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:/
app/store/
On 2013/03/20 15:53:39, matthew.scott wrote:
> Super minor: inline comment as above.
Done.
Preview Diff
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 | +})(); |
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: debug.js env/base. js env/fakebackend .js env/sandbox. js fakebackend. js sandbox. js
A [revision details]
M app/modules-
M app/store/
M app/store/
A app/store/
M test/index.html
M test/test_app.js
M test/test_env.js
M test/test_
A test/test_