Merge lp:~benji/juju-gui/login into lp:juju-gui/experimental

Proposed by Benji York
Status: Merged
Merged at revision: 304
Proposed branch: lp:~benji/juju-gui/login
Merge into: lp:juju-gui/experimental
Diff against target: 536 lines (+286/-32)
10 files modified
app/app.js (+49/-0)
app/modules-debug.js (+5/-0)
app/modules-prod.js (+1/-0)
app/store/env.js (+49/-7)
app/views/login.js (+59/-0)
test/index.html (+26/-22)
test/test_app.js (+7/-1)
test/test_app_hotkeys.js (+2/-1)
test/test_login.js (+86/-0)
test/test_service_view.js (+2/-1)
To merge this branch: bzr merge lp:~benji/juju-gui/login
Reviewer Review Type Date Requested Status
Juju GUI Hackers Pending
Review via email: mp+141133@code.launchpad.net

Description of the change

Add user login support.

Future work: we need to implement a real UI for this. At the moment this
branch uses prompt() calls to generate modal dialogs. Also, this will
probably need slight tweaking once the backend actually requires
authorization.

https://codereview.appspot.com/7007047/

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

needs work

https://codereview.appspot.com/7007047/diff/1/app/app.js
File app/app.js (right):

https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode544
app/app.js:544: if (!view.waiting && !view.userIsAuthenticated) {
this content belongs in the app, not the view. the app needs to re-login
on a socket reconnect.

https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode547
app/app.js:547: next();
this is an either or proposition, not an and. we either display the
login or the app, not overlay the login over the app, which will be
broken without a working ws.

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

https://codereview.appspot.com/7007047/diff/1/app/store/env.js#newcode77
app/store/env.js:77: this.set('serverReady', true);
instead of introducing another value just move connected down here.

https://codereview.appspot.com/7007047/

Revision history for this message
Brad Crittenden (bac) wrote :

This looks ok to me, given the changes Kapil requests. I'll look again
after those changes are made.

https://codereview.appspot.com/7007047/diff/1/app/app.js
File app/app.js (right):

https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode530
app/app.js:530: * @param {Object} res ???
Why the duplicate 'res' and the ???

https://codereview.appspot.com/7007047/diff/1/app/views/login.js
File app/views/login.js (right):

https://codereview.appspot.com/7007047/diff/1/app/views/login.js#newcode3
app/views/login.js:3: var no_login_prompts = false;
Why not camelCase?

https://codereview.appspot.com/7007047/diff/1/app/views/login.js#newcode3
app/views/login.js:3: var no_login_prompts = false;
Why not camelCase?

https://codereview.appspot.com/7007047/diff/1/app/views/login.js#newcode27
app/views/login.js:27: * React to the results of sennding a login
message to the server.
typo: sending

https://codereview.appspot.com/7007047/diff/1/test/test_login_view.js
File test/test_login_view.js (right):

https://codereview.appspot.com/7007047/diff/1/test/test_login_view.js#newcode147
test/test_login_view.js:147: // If there are know credentials that are
not known to be bad (they are
s/know/no/

https://codereview.appspot.com/7007047/

lp:~benji/juju-gui/login updated
299. By Benji York

checkpoint -- some tests still failing

300. By Benji York

merge trunk and fix tests

301. By Benji York

checkpoint

302. By Benji York

review fixes

Revision history for this message
Kapil Thangavelu (hazmat) wrote :
Download full text (4.7 KiB)

On Wed, Jan 2, 2013 at 2:43 PM, <email address hidden> wrote:

> Please take a look.
>
>
>
> https://codereview.appspot.**com/7007047/diff/1/app/app.js<https://codereview.appspot.com/7007047/diff/1/app/app.js>
> File app/app.js (right):
>
> https://codereview.appspot.**com/7007047/diff/1/app/app.js#**newcode530<https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode530>
> app/app.js:530: * @param {Object} res ???
> On 2013/01/02 14:45:03, bac wrote:
>
>> Why the duplicate 'res' and the ???
>>
>
> Done.
>
> https://codereview.appspot.**com/7007047/diff/1/app/app.js#**newcode544<https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode544>
> app/app.js:544: if (!view.waiting && !view.userIsAuthenticated) {
> On 2012/12/21 21:06:18, hazmat wrote:
>
>> this content belongs in the app, not the view. the app needs to
>>
> re-login on a
>
>> socket reconnect.
>>
>
> It appears to me that a socket reconnect is a "reboot" situation for the
> app.
>

i think we have a misunderstanding here, afaics whether or not its a
'reboot' situation has nothing to do with the app's responsibility to hold
on to the credential data or authenticating on the websocket.

 That is, since we can not retry the operation that was ongoing
> when the reconnect happened, we need to restart the app from scratch in
> order to retain consistency. Am I missing a way to retry the last
> operation after a reconnect?
>
>
we can and do track in flight rpc operations, and wait till success ack
before modifying local state. yes, the local state for delta sync data is
effectively reset on reconnect already but that has nothing to do with
storing credentials for authenticating the user again. what's the
concern/usage with retrying operations?

> https://codereview.appspot.**com/7007047/diff/1/app/app.js#**newcode547<https://codereview.appspot.com/7007047/diff/1/app/app.js#newcode547>
> app/app.js:547: next();
> On 2012/12/21 21:06:18, hazmat wrote:
>
>> this is an either or proposition, not an and. we either display the
>>
> login or the
>
>> app, not overlay the login over the app, which will be broken without
>>
> a working
>
>> ws.
>>
>
> That was my intent, but without a backend that actually requires logins
> I couldn't be sure it is working correctly so I settled on this
> compromise until then.
>
>
the backend requiring the login is irrelevant to the logic that needs to be
done here. the front end app should never toss an error on an
uauthenticated connection. the app knows the connection status and whether
auth has been performed on it, it knows if credentials have been provided
it. If they haven't it displays the login form. If they have it logins the
user in. the user should never see a not authenticated error or the login
form multiple times in a single session (which is incidentally another
reason why the login view shouldn't be persistent).

> https://codereview.appspot.**com/7007047/diff/1/app/store/**env.js<https://codereview.appspot.com/7007047/diff/1/app/store/env.js>
> File app/store/env.js (right):
>

> https://codereview.appspot.**com/7007047/diff/1/app/store/**
> env.js#newcode77<https://codereview.appspot.com/7007047/diff/1/app/store/env.js#ne...

Read more...

lp:~benji/juju-gui/login updated
303. By Benji York

review fix

304. By Benji York

checkpoint

305. By Benji York

checkpoint

306. By Benji York

checkpoint

307. By Benji York

merge trunk

308. By Benji York

checkpoint

309. By Benji York

checkpoint - tests pass

310. By Benji York

pre-review fixes

311. By Benji York

fix lint

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

If you want me to look at this again I would be happy to, but otherwise
land with changes.

Thank you

Gary

https://codereview.appspot.com/7007047/diff/15001/app/store/env.js
File app/store/env.js (right):

https://codereview.appspot.com/7007047/diff/15001/app/store/env.js#newcode87
app/store/env.js:87: this.set('defaultSeries', msg.default_series);
The above two lines are supposed to be added to handleLoginEvent, and
removed from here entirely later. Unfortunately, the current rapi
implementation does not support this.

https://codereview.appspot.com/7007047/diff/15001/app/store/env.js#newcode113
app/store/env.js:113: }
The implementation does not do what was specced, but we should look
forward to it anyway, I think.

else {
   if (evt.data.provider_type) {
     this.set('providerType', msg.data.provider_type);
   }
   if (evt.data.default_series) {
     this.set('defaultSeries', msg.data.default_series);
   }
}

In the future we should be able to remove the conditional parts of that,
and rely on the two values existing.

https://codereview.appspot.com/7007047/diff/15001/app/store/env.js#newcode223
app/store/env.js:223: window.setTimeout(Y.bind(this.login, this), 500);
We talked about this and you liked the idea of connecting this to an
event instead (event fired by login attempt).

https://codereview.appspot.com/7007047/diff/15001/test/index.html
File test/index.html (right):

https://codereview.appspot.com/7007047/diff/15001/test/index.html#newcode37
test/index.html:37: // *after* the test runner is invoked. In which
case the test runner
"In this case,"

https://codereview.appspot.com/7007047/

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

Please make a high priority card for the UI implementation, btw

https://codereview.appspot.com/7007047/

lp:~benji/juju-gui/login updated
312. By Benji York

review fixes

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/app.js'
2--- app/app.js 2012-12-19 13:45:10 +0000
3+++ app/app.js 2013-01-07 21:41:21 +0000
4@@ -29,6 +29,11 @@
5 preserve: true
6 },
7
8+ login: {
9+ type: 'juju.views.login',
10+ preserve: false
11+ },
12+
13 service: {
14 type: 'juju.views.service',
15 preserve: false,
16@@ -238,6 +243,9 @@
17 // When the provider type becomes available, display it.
18 this.env.after('providerTypeChange', this.onProviderTypeChange);
19
20+ // Once the user logs in we need to redraw.
21+ this.env.after('login', this.dispatch, this);
22+
23 // Feed environment changes directly into the database.
24 this.env.on('delta', this.db.on_delta, this.db);
25
26@@ -517,6 +525,45 @@
27 },
28
29 /**
30+ * Ensure that the current user has authenticated.
31+ *
32+ * @method check_user_credentials
33+ * @param {Object} req The request.
34+ * @param {Object} res ???
35+ * @param {Object} next The next route handler.
36+ *
37+ */
38+ check_user_credentials: function(req, res, next) {
39+ var viewInfo = this.getViewInfo('login');
40+ if (!viewInfo.instance) {
41+ viewInfo.instance = new views.LoginView({
42+ app: this,
43+ env: this.env
44+ });
45+ }
46+ var view = viewInfo.instance;
47+ // If there are no stored credentials the user is prompted for some.
48+ var user = this.env.get('user');
49+ var password = this.env.get('password');
50+ if (!Y.Lang.isValue(user) || !Y.Lang.isValue(password)) {
51+ view.promptUser();
52+ }
53+ // If there are credentials available and there has not been a successful
54+ // login attempt and we are not waiting on a login attempt, try to log in.
55+ if (Y.Lang.isValue(user) && Y.Lang.isValue(password) &&
56+ !this.env.waiting && !this.env.userIsAuthenticated) {
57+ this.env.login();
58+ return;
59+ }
60+ // If there has not been a successful login attempt, do not let the route
61+ // dispatch proceed.
62+ if (this.env.waiting || !this.env.userIsAuthenticated) {
63+ return;
64+ }
65+ next();
66+ },
67+
68+ /**
69 * Display the provider type.
70 *
71 * The provider type arrives asynchronously. Instead of updating the
72@@ -709,6 +756,7 @@
73 charm_store: {},
74 routes: {
75 value: [
76+ { path: '*', callback: 'check_user_credentials'},
77 { path: '*', callback: 'show_notifications_view'},
78 { path: '/charms/', callback: 'show_charm_collection'},
79 { path: '/charms/*charm_store_path',
80@@ -746,6 +794,7 @@
81 }, '0.5.2', {
82 requires: [
83 'juju-models',
84+ 'juju-notification-controller',
85 'juju-charm-models',
86 'juju-views',
87 'juju-controllers',
88
89=== modified file 'app/modules-debug.js'
90--- app/modules-debug.js 2012-12-21 12:52:30 +0000
91+++ app/modules-debug.js 2013-01-07 21:41:21 +0000
92@@ -96,6 +96,10 @@
93 fullpath: '/juju-ui/views/environment.js'
94 },
95
96+ 'juju-view-login': {
97+ fullpath: '/juju-ui/views/login.js'
98+ },
99+
100 'juju-view-service': {
101 fullpath: '/juju-ui/views/service.js'
102 },
103@@ -124,6 +128,7 @@
104 'juju-view-utils',
105 'juju-topology',
106 'juju-view-environment',
107+ 'juju-view-login',
108 'juju-view-service',
109 'juju-view-unit',
110 'juju-view-charm',
111
112=== modified file 'app/modules-prod.js'
113--- app/modules-prod.js 2012-12-13 02:43:15 +0000
114+++ app/modules-prod.js 2013-01-07 21:41:21 +0000
115@@ -17,6 +17,7 @@
116 'juju-topology',
117 'juju-view-utils',
118 'juju-view-environment',
119+ 'juju-view-login',
120 'juju-view-service',
121 'juju-view-unit',
122 'juju-view-charm',
123
124=== modified file 'app/store/env.js'
125--- app/store/env.js 2012-10-15 13:20:38 +0000
126+++ app/store/env.js 2013-01-07 21:41:21 +0000
127@@ -1,5 +1,8 @@
128 'use strict';
129
130+// A global.
131+var noLogin;
132+
133 YUI.add('juju-env', function(Y) {
134
135 function Environment(config) {
136@@ -12,6 +15,8 @@
137 Environment.ATTRS = {
138 'socket_url': {},
139 'conn': {},
140+ 'user': {},
141+ 'password': {},
142 'connected': {value: false},
143 'debug': {value: false}
144 };
145@@ -30,6 +35,11 @@
146 this._counter = 0;
147 // mapping txn-id callback if any.
148 this._txn_callbacks = {};
149+ // Consider the user unauthenticated until proven otherwise.
150+ this.userIsAuthenticated = false;
151+ // When the server tells us the outcome of a login attempt we record
152+ // the result.
153+ this.on('login', this.handleLoginEvent, this);
154 },
155
156 destructor: function() {
157@@ -58,7 +68,6 @@
158
159 on_open: function(data) {
160 console.log('Env: Connected');
161- this.set('connected', true);
162 },
163
164 on_close: function(data) {
165@@ -68,21 +77,37 @@
166
167 on_message: function(evt) {
168 console.log('Env: Receive', evt.data);
169-
170 var msg = Y.JSON.parse(evt.data);
171+ // The "ready" attribute indicates that this is a server's initial
172+ // greeting. It provides a few initial values that we care about.
173 if (msg.ready) {
174- // The "ready" attribute indicates that this is a server's initial
175- // greeting. It provides a few initial values that we care about.
176+ console.log('Env: Handshake Complete');
177+ this.set('connected', true);
178 this.set('providerType', msg.provider_type);
179 this.set('defaultSeries', msg.default_series);
180- }
181- if (msg.version === 0) {
182- console.log('Env: Handshake Complete');
183 return;
184 }
185 this.fire('msg', msg);
186 },
187
188+ /**
189+ * React to the results of sending a login message to the server.
190+ *
191+ * @method handleLoginEvent
192+ * @param {Object} evt The event to which we are responding.
193+ * @return {undefined} Nothing.
194+ */
195+ handleLoginEvent: function(evt) {
196+ // We are only interested in the responses to login events.
197+ this.userIsAuthenticated = !!evt.data.result;
198+ this.waiting = false;
199+ // If the credentials were rejected remove them.
200+ if (!this.userIsAuthenticated) {
201+ this.set('user', undefined);
202+ this.set('password', undefined);
203+ }
204+ },
205+
206 dispatch_result: function(data) {
207 console.log('Env: Dispatch Result', data);
208 this._dispatch_rpc_result(data);
209@@ -174,6 +199,23 @@
210 this._send_rpc({'op': 'expose', 'service_name': service}, callback);
211 },
212
213+ /**
214+ * Attempt to log the user in. Credentials must have been previously
215+ * stored on the environment. If not, this method will schedule a call to
216+ * itself in the future in order to try again.
217+ *
218+ * @return {undefined} Nothing.
219+ */
220+ login: function() {
221+ // If the user is already authenticated there is nothing to do.
222+ if (this.userIsAuthenticated) {
223+ return;
224+ }
225+ var user = this.get('user');
226+ var password = this.get('password');
227+ this._send_rpc({op: 'login', user: user, password: password});
228+ },
229+
230 unexpose: function(service, callback) {
231 this._send_rpc({'op': 'unexpose', 'service_name': service}, callback);
232 },
233
234=== added file 'app/views/login.js'
235--- app/views/login.js 1970-01-01 00:00:00 +0000
236+++ app/views/login.js 2013-01-07 21:41:21 +0000
237@@ -0,0 +1,59 @@
238+'use strict';
239+
240+// Should login prompts be presented? Turned on for testing.
241+var noLogin = noLogin || false;
242+
243+YUI.add('juju-view-login', function(Y) {
244+
245+ var views = Y.namespace('juju.views');
246+ var Templates = views.Templates;
247+
248+ var LoginView = Y.Base.create('LoginView', Y.View, [views.JujuBaseView], {
249+ // This is so tests can easily determine if the user was prompted.
250+ _prompted: false,
251+
252+ /**
253+ * Prompt the user for input.
254+ *
255+ * Does nothing if a special global flag has been set. This is used so
256+ * tests do not generate prompts.
257+ *
258+ * @method _prompt
259+ * @param {String} message The message to display to the user.
260+ * @return {String} The string the user typed.
261+ */
262+ _prompt: function(message) {
263+ // noLogin is a global.
264+ if (noLogin) {
265+ return null;
266+ }
267+ return window.prompt(message);
268+ },
269+
270+ /**
271+ * Prompt the user for their user name and password.
272+ *
273+ * @method promptUser
274+ * @return {undefined} Nothing.
275+ */
276+ promptUser: function() {
277+ this._prompted = true;
278+ var env = this.get('env');
279+ // The user's name is always "admin".
280+ env.set('user', 'admin');
281+ env.set('password', this._prompt('Password'));
282+ }
283+
284+ });
285+
286+ views.LoginView = LoginView;
287+
288+}, '0.1.0', {
289+ requires: [
290+ 'view',
291+ 'juju-view-utils',
292+ 'node',
293+ 'handlebars'
294+ ]
295+});
296+
297
298=== modified file 'test/index.html'
299--- test/index.html 2012-12-21 12:06:26 +0000
300+++ test/index.html 2013-01-07 21:41:21 +0000
301@@ -13,10 +13,34 @@
302 <script src="assets/mocha.js"></script>
303 <script src="utils.js"></script>
304 <script>
305+ noLogin = true;
306 var assert = chai.assert,
307 expect = chai.expect
308 should = chai.should();
309- mocha.setup({'ui': 'bdd', 'ignoreLeaks': false})
310+ mocha.setup({ui: 'bdd', ignoreLeaks: false})
311+ </script>
312+
313+ <script>
314+ YUI().use('node', 'event', function(Y) {
315+ var config = GlobalConfig;
316+ for (group in config.groups) {
317+ var group = config.groups[group];
318+ for (m in group.modules) {
319+ var resource = group.modules[m];
320+ if (!m || !resource.fullpath) {
321+ continue
322+ }
323+ resource.fullpath = resource.fullpath.replace(
324+ '/juju-ui/', '../juju-ui/', 1);
325+ // If we load modules asyncronously then the module loading may take
326+ // so long that the test definitions (and before/after calls) happen
327+ // *after* the test runner is invoked. In this case the test runner
328+ // will not know about the tests and therefore not run them.
329+ resource.async = false;
330+ }
331+ }
332+ Y.on('domready', mocha.run);
333+ });
334 </script>
335
336 <script src="test_d3_components.js"></script>
337@@ -33,6 +57,7 @@
338 <script src="test_service_config_view.js"></script>
339 <script src="test_service_view.js"></script>
340 <script src="test_utils.js"></script>
341+ <script src="test_login.js"></script>
342 <script src="test_charm_panel.js"></script>
343 <script src="test_charm_configuration.js"></script>
344 <script src="test_console.js"></script>
345@@ -42,27 +67,6 @@
346 <script src="test_app_hotkeys.js"></script>
347 <script src="test_notifier_widget.js"></script>
348
349- <script>
350- YUI().use('node', 'event', function(Y) {
351- Y.on('domready', function() {
352-
353- var config = GlobalConfig;
354- for (group in config.groups) {
355- var group = config.groups[group];
356- for (m in group.modules) {
357- var resource = group.modules[m];
358- if (!m || !resource.fullpath) {
359- continue
360- }
361- resource.fullpath = resource.fullpath.replace(
362- '/juju-ui/', '../juju-ui/', 1);
363- }
364- }
365- // Load before test runner
366- mocha.run();
367- });
368- });
369- </script>
370
371 </head>
372
373
374=== modified file 'test/test_app.js'
375--- test/test_app.js 2012-12-03 20:24:44 +0000
376+++ test/test_app.js 2013-01-07 21:41:21 +0000
377@@ -119,6 +119,7 @@
378 });
379
380 YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
381+
382 describe('Application Connection State', function() {
383 var container;
384
385@@ -128,7 +129,7 @@
386
387 it('should be able to handle env connection status changes', function() {
388 var juju = Y.namespace('juju'),
389- conn = new(Y.namespace('juju-tests.utils')).SocketStub(),
390+ conn = new (Y.namespace('juju-tests.utils')).SocketStub(),
391 env = new juju.Environment({conn: conn}),
392 app = new Y.juju.App({env: env, container: container}),
393 reset_called = false,
394@@ -153,11 +154,14 @@
395 };
396 env.connect();
397 conn.open();
398+ // We need to fake the connection event.
399+ env.set('connected', true);
400 reset_called.should.equal(true);
401
402 // trigger a second time and verify
403 reset_called = false;
404 conn.open();
405+ env.set('connected', true);
406 reset_called.should.equal(true);
407 });
408
409@@ -229,6 +233,8 @@
410 env.get_endpoints = function(services, callback) {
411 get_endpoints_count += 1;
412 };
413+ // We need to fake the connection event.
414+ env.set('connected', true);
415 // Inject default data, should only get_endpoints once.
416 injectData(app);
417 get_endpoints_count.should.equal(1);
418
419=== modified file 'test/test_app_hotkeys.js'
420--- test/test_app_hotkeys.js 2012-11-27 14:25:32 +0000
421+++ test/test_app_hotkeys.js 2013-01-07 21:41:21 +0000
422@@ -9,7 +9,8 @@
423 var env = {
424 after: function() {},
425 get: function() {},
426- on: function() {}
427+ on: function() {},
428+ set: function() {}
429 };
430 windowNode = Y.one(window);
431 app = new Y.juju.App({
432
433=== added file 'test/test_login.js'
434--- test/test_login.js 1970-01-01 00:00:00 +0000
435+++ test/test_login.js 2013-01-07 21:41:21 +0000
436@@ -0,0 +1,86 @@
437+'use strict';
438+
439+(function() {
440+
441+ var requires = ['node', 'juju-gui', 'juju-views', 'juju-tests-utils'];
442+ var Y = YUI(GlobalConfig).use(requires);
443+
444+ describe('environment login support', function() {
445+ var conn, env, utils, juju, makeLoginView, views, app;
446+ var test = it; // We aren't really doing BDD so let's be more direct.
447+
448+ before(function() {
449+ utils = Y.namespace('juju-tests.utils');
450+ juju = Y.namespace('juju');
451+ });
452+
453+ beforeEach(function() {
454+ var container = Y.Node.create('<div/>');
455+ conn = new utils.SocketStub();
456+ env = new juju.Environment({conn: conn});
457+ env.connect();
458+ conn.open();
459+ // env.set('connected', true);
460+ });
461+
462+ afterEach(function() {
463+ env.destroy();
464+ });
465+
466+ test('the user is initially assumed to be unauthenticated', function() {
467+ assert.equal(env.userIsAuthenticated, false);
468+ });
469+
470+ test('successful login event marks user as authenticated', function() {
471+ var evt = {data: {op: 'login', result: true}};
472+ env.handleLoginEvent(evt);
473+ assert.equal(env.userIsAuthenticated, true);
474+ });
475+
476+ test('unsuccessful login event keeps user unauthenticated', function() {
477+ var evt = {data: {op: 'login'}};
478+ env.handleLoginEvent(evt);
479+ assert.equal(env.userIsAuthenticated, false);
480+ });
481+
482+ test('bad credentials are removed', function() {
483+ var evt = {data: {op: 'login'}};
484+ env.handleLoginEvent(evt);
485+ assert.equal(env.get('user'), undefined);
486+ assert.equal(env.get('password'), undefined);
487+ });
488+
489+ test('credentials passed to the constructor are stored', function() {
490+ var user = 'Will Smith';
491+ var password = 'I am legend!';
492+ var env = new juju.Environment({
493+ user: user,
494+ password: password,
495+ conn: conn
496+ });
497+ assert.equal(env.get('user'), user);
498+ assert.equal(env.get('password'), password);
499+ });
500+
501+ test('login requests are sent in response to a connection', function() {
502+ env.fire('log', {op: 'login', data: {}});
503+ });
504+
505+ test('if already authenticated, login() is a no-op', function() {
506+ env.userIsAuthenticated = true;
507+ env.login();
508+ assert.equal(conn.last_message(), undefined);
509+ });
510+
511+ test('with credentials set, login() sends an RPC message', function() {
512+ env.set('user', 'user');
513+ env.set('password', 'password');
514+ env.login();
515+ assert.equal(conn.last_message().op, 'login');
516+ assert.equal(conn.last_message().user, 'user');
517+ assert.equal(conn.last_message().password, 'password');
518+ });
519+
520+ });
521+
522+})();
523
524=== modified file 'test/test_service_view.js'
525--- test/test_service_view.js 2012-11-08 18:16:07 +0000
526+++ test/test_service_view.js 2013-01-07 21:41:21 +0000
527@@ -23,7 +23,8 @@
528 env = new (Y.namespace('juju')).Environment({conn: conn});
529 env.connect();
530 conn.open();
531- container = Y.Node.create('<div id="test-container" />');
532+ container = Y.Node.create('<div/>')
533+ .hide();
534 Y.one('#main').append(container);
535 db = new models.Database();
536 charm = new models.Charm({id: 'cs:precise/mysql-7', description: 'A DB'});

Subscribers

People subscribed via source and target branches