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
=== modified file 'app/app.js'
--- app/app.js 2012-12-19 13:45:10 +0000
+++ app/app.js 2013-01-07 21:41:21 +0000
@@ -29,6 +29,11 @@
29 preserve: true29 preserve: true
30 },30 },
3131
32 login: {
33 type: 'juju.views.login',
34 preserve: false
35 },
36
32 service: {37 service: {
33 type: 'juju.views.service',38 type: 'juju.views.service',
34 preserve: false,39 preserve: false,
@@ -238,6 +243,9 @@
238 // When the provider type becomes available, display it.243 // When the provider type becomes available, display it.
239 this.env.after('providerTypeChange', this.onProviderTypeChange);244 this.env.after('providerTypeChange', this.onProviderTypeChange);
240245
246 // Once the user logs in we need to redraw.
247 this.env.after('login', this.dispatch, this);
248
241 // Feed environment changes directly into the database.249 // Feed environment changes directly into the database.
242 this.env.on('delta', this.db.on_delta, this.db);250 this.env.on('delta', this.db.on_delta, this.db);
243251
@@ -517,6 +525,45 @@
517 },525 },
518526
519 /**527 /**
528 * Ensure that the current user has authenticated.
529 *
530 * @method check_user_credentials
531 * @param {Object} req The request.
532 * @param {Object} res ???
533 * @param {Object} next The next route handler.
534 *
535 */
536 check_user_credentials: function(req, res, next) {
537 var viewInfo = this.getViewInfo('login');
538 if (!viewInfo.instance) {
539 viewInfo.instance = new views.LoginView({
540 app: this,
541 env: this.env
542 });
543 }
544 var view = viewInfo.instance;
545 // If there are no stored credentials the user is prompted for some.
546 var user = this.env.get('user');
547 var password = this.env.get('password');
548 if (!Y.Lang.isValue(user) || !Y.Lang.isValue(password)) {
549 view.promptUser();
550 }
551 // If there are credentials available and there has not been a successful
552 // login attempt and we are not waiting on a login attempt, try to log in.
553 if (Y.Lang.isValue(user) && Y.Lang.isValue(password) &&
554 !this.env.waiting && !this.env.userIsAuthenticated) {
555 this.env.login();
556 return;
557 }
558 // If there has not been a successful login attempt, do not let the route
559 // dispatch proceed.
560 if (this.env.waiting || !this.env.userIsAuthenticated) {
561 return;
562 }
563 next();
564 },
565
566 /**
520 * Display the provider type.567 * Display the provider type.
521 *568 *
522 * The provider type arrives asynchronously. Instead of updating the569 * The provider type arrives asynchronously. Instead of updating the
@@ -709,6 +756,7 @@
709 charm_store: {},756 charm_store: {},
710 routes: {757 routes: {
711 value: [758 value: [
759 { path: '*', callback: 'check_user_credentials'},
712 { path: '*', callback: 'show_notifications_view'},760 { path: '*', callback: 'show_notifications_view'},
713 { path: '/charms/', callback: 'show_charm_collection'},761 { path: '/charms/', callback: 'show_charm_collection'},
714 { path: '/charms/*charm_store_path',762 { path: '/charms/*charm_store_path',
@@ -746,6 +794,7 @@
746}, '0.5.2', {794}, '0.5.2', {
747 requires: [795 requires: [
748 'juju-models',796 'juju-models',
797 'juju-notification-controller',
749 'juju-charm-models',798 'juju-charm-models',
750 'juju-views',799 'juju-views',
751 'juju-controllers',800 'juju-controllers',
752801
=== modified file 'app/modules-debug.js'
--- app/modules-debug.js 2012-12-21 12:52:30 +0000
+++ app/modules-debug.js 2013-01-07 21:41:21 +0000
@@ -96,6 +96,10 @@
96 fullpath: '/juju-ui/views/environment.js'96 fullpath: '/juju-ui/views/environment.js'
97 },97 },
9898
99 'juju-view-login': {
100 fullpath: '/juju-ui/views/login.js'
101 },
102
99 'juju-view-service': {103 'juju-view-service': {
100 fullpath: '/juju-ui/views/service.js'104 fullpath: '/juju-ui/views/service.js'
101 },105 },
@@ -124,6 +128,7 @@
124 'juju-view-utils',128 'juju-view-utils',
125 'juju-topology',129 'juju-topology',
126 'juju-view-environment',130 'juju-view-environment',
131 'juju-view-login',
127 'juju-view-service',132 'juju-view-service',
128 'juju-view-unit',133 'juju-view-unit',
129 'juju-view-charm',134 'juju-view-charm',
130135
=== modified file 'app/modules-prod.js'
--- app/modules-prod.js 2012-12-13 02:43:15 +0000
+++ app/modules-prod.js 2013-01-07 21:41:21 +0000
@@ -17,6 +17,7 @@
17 'juju-topology',17 'juju-topology',
18 'juju-view-utils',18 'juju-view-utils',
19 'juju-view-environment',19 'juju-view-environment',
20 'juju-view-login',
20 'juju-view-service',21 'juju-view-service',
21 'juju-view-unit',22 'juju-view-unit',
22 'juju-view-charm',23 'juju-view-charm',
2324
=== modified file 'app/store/env.js'
--- app/store/env.js 2012-10-15 13:20:38 +0000
+++ app/store/env.js 2013-01-07 21:41:21 +0000
@@ -1,5 +1,8 @@
1'use strict';1'use strict';
22
3// A global.
4var noLogin;
5
3YUI.add('juju-env', function(Y) {6YUI.add('juju-env', function(Y) {
47
5 function Environment(config) {8 function Environment(config) {
@@ -12,6 +15,8 @@
12 Environment.ATTRS = {15 Environment.ATTRS = {
13 'socket_url': {},16 'socket_url': {},
14 'conn': {},17 'conn': {},
18 'user': {},
19 'password': {},
15 'connected': {value: false},20 'connected': {value: false},
16 'debug': {value: false}21 'debug': {value: false}
17 };22 };
@@ -30,6 +35,11 @@
30 this._counter = 0;35 this._counter = 0;
31 // mapping txn-id callback if any.36 // mapping txn-id callback if any.
32 this._txn_callbacks = {};37 this._txn_callbacks = {};
38 // Consider the user unauthenticated until proven otherwise.
39 this.userIsAuthenticated = false;
40 // When the server tells us the outcome of a login attempt we record
41 // the result.
42 this.on('login', this.handleLoginEvent, this);
33 },43 },
3444
35 destructor: function() {45 destructor: function() {
@@ -58,7 +68,6 @@
5868
59 on_open: function(data) {69 on_open: function(data) {
60 console.log('Env: Connected');70 console.log('Env: Connected');
61 this.set('connected', true);
62 },71 },
6372
64 on_close: function(data) {73 on_close: function(data) {
@@ -68,21 +77,37 @@
6877
69 on_message: function(evt) {78 on_message: function(evt) {
70 console.log('Env: Receive', evt.data);79 console.log('Env: Receive', evt.data);
71
72 var msg = Y.JSON.parse(evt.data);80 var msg = Y.JSON.parse(evt.data);
81 // The "ready" attribute indicates that this is a server's initial
82 // greeting. It provides a few initial values that we care about.
73 if (msg.ready) {83 if (msg.ready) {
74 // The "ready" attribute indicates that this is a server's initial84 console.log('Env: Handshake Complete');
75 // greeting. It provides a few initial values that we care about.85 this.set('connected', true);
76 this.set('providerType', msg.provider_type);86 this.set('providerType', msg.provider_type);
77 this.set('defaultSeries', msg.default_series);87 this.set('defaultSeries', msg.default_series);
78 }
79 if (msg.version === 0) {
80 console.log('Env: Handshake Complete');
81 return;88 return;
82 }89 }
83 this.fire('msg', msg);90 this.fire('msg', msg);
84 },91 },
8592
93 /**
94 * React to the results of sending a login message to the server.
95 *
96 * @method handleLoginEvent
97 * @param {Object} evt The event to which we are responding.
98 * @return {undefined} Nothing.
99 */
100 handleLoginEvent: function(evt) {
101 // We are only interested in the responses to login events.
102 this.userIsAuthenticated = !!evt.data.result;
103 this.waiting = false;
104 // If the credentials were rejected remove them.
105 if (!this.userIsAuthenticated) {
106 this.set('user', undefined);
107 this.set('password', undefined);
108 }
109 },
110
86 dispatch_result: function(data) {111 dispatch_result: function(data) {
87 console.log('Env: Dispatch Result', data);112 console.log('Env: Dispatch Result', data);
88 this._dispatch_rpc_result(data);113 this._dispatch_rpc_result(data);
@@ -174,6 +199,23 @@
174 this._send_rpc({'op': 'expose', 'service_name': service}, callback);199 this._send_rpc({'op': 'expose', 'service_name': service}, callback);
175 },200 },
176201
202 /**
203 * Attempt to log the user in. Credentials must have been previously
204 * stored on the environment. If not, this method will schedule a call to
205 * itself in the future in order to try again.
206 *
207 * @return {undefined} Nothing.
208 */
209 login: function() {
210 // If the user is already authenticated there is nothing to do.
211 if (this.userIsAuthenticated) {
212 return;
213 }
214 var user = this.get('user');
215 var password = this.get('password');
216 this._send_rpc({op: 'login', user: user, password: password});
217 },
218
177 unexpose: function(service, callback) {219 unexpose: function(service, callback) {
178 this._send_rpc({'op': 'unexpose', 'service_name': service}, callback);220 this._send_rpc({'op': 'unexpose', 'service_name': service}, callback);
179 },221 },
180222
=== added file 'app/views/login.js'
--- app/views/login.js 1970-01-01 00:00:00 +0000
+++ app/views/login.js 2013-01-07 21:41:21 +0000
@@ -0,0 +1,59 @@
1'use strict';
2
3// Should login prompts be presented? Turned on for testing.
4var noLogin = noLogin || false;
5
6YUI.add('juju-view-login', function(Y) {
7
8 var views = Y.namespace('juju.views');
9 var Templates = views.Templates;
10
11 var LoginView = Y.Base.create('LoginView', Y.View, [views.JujuBaseView], {
12 // This is so tests can easily determine if the user was prompted.
13 _prompted: false,
14
15 /**
16 * Prompt the user for input.
17 *
18 * Does nothing if a special global flag has been set. This is used so
19 * tests do not generate prompts.
20 *
21 * @method _prompt
22 * @param {String} message The message to display to the user.
23 * @return {String} The string the user typed.
24 */
25 _prompt: function(message) {
26 // noLogin is a global.
27 if (noLogin) {
28 return null;
29 }
30 return window.prompt(message);
31 },
32
33 /**
34 * Prompt the user for their user name and password.
35 *
36 * @method promptUser
37 * @return {undefined} Nothing.
38 */
39 promptUser: function() {
40 this._prompted = true;
41 var env = this.get('env');
42 // The user's name is always "admin".
43 env.set('user', 'admin');
44 env.set('password', this._prompt('Password'));
45 }
46
47 });
48
49 views.LoginView = LoginView;
50
51}, '0.1.0', {
52 requires: [
53 'view',
54 'juju-view-utils',
55 'node',
56 'handlebars'
57 ]
58});
59
060
=== modified file 'test/index.html'
--- test/index.html 2012-12-21 12:06:26 +0000
+++ test/index.html 2013-01-07 21:41:21 +0000
@@ -13,10 +13,34 @@
13 <script src="assets/mocha.js"></script>13 <script src="assets/mocha.js"></script>
14 <script src="utils.js"></script>14 <script src="utils.js"></script>
15 <script>15 <script>
16 noLogin = true;
16 var assert = chai.assert,17 var assert = chai.assert,
17 expect = chai.expect18 expect = chai.expect
18 should = chai.should();19 should = chai.should();
19 mocha.setup({'ui': 'bdd', 'ignoreLeaks': false})20 mocha.setup({ui: 'bdd', ignoreLeaks: false})
21 </script>
22
23 <script>
24 YUI().use('node', 'event', function(Y) {
25 var config = GlobalConfig;
26 for (group in config.groups) {
27 var group = config.groups[group];
28 for (m in group.modules) {
29 var resource = group.modules[m];
30 if (!m || !resource.fullpath) {
31 continue
32 }
33 resource.fullpath = resource.fullpath.replace(
34 '/juju-ui/', '../juju-ui/', 1);
35 // If we load modules asyncronously then the module loading may take
36 // so long that the test definitions (and before/after calls) happen
37 // *after* the test runner is invoked. In this case the test runner
38 // will not know about the tests and therefore not run them.
39 resource.async = false;
40 }
41 }
42 Y.on('domready', mocha.run);
43 });
20 </script>44 </script>
2145
22 <script src="test_d3_components.js"></script>46 <script src="test_d3_components.js"></script>
@@ -33,6 +57,7 @@
33 <script src="test_service_config_view.js"></script>57 <script src="test_service_config_view.js"></script>
34 <script src="test_service_view.js"></script>58 <script src="test_service_view.js"></script>
35 <script src="test_utils.js"></script>59 <script src="test_utils.js"></script>
60 <script src="test_login.js"></script>
36 <script src="test_charm_panel.js"></script>61 <script src="test_charm_panel.js"></script>
37 <script src="test_charm_configuration.js"></script>62 <script src="test_charm_configuration.js"></script>
38 <script src="test_console.js"></script>63 <script src="test_console.js"></script>
@@ -42,27 +67,6 @@
42 <script src="test_app_hotkeys.js"></script>67 <script src="test_app_hotkeys.js"></script>
43 <script src="test_notifier_widget.js"></script>68 <script src="test_notifier_widget.js"></script>
4469
45 <script>
46 YUI().use('node', 'event', function(Y) {
47 Y.on('domready', function() {
48
49 var config = GlobalConfig;
50 for (group in config.groups) {
51 var group = config.groups[group];
52 for (m in group.modules) {
53 var resource = group.modules[m];
54 if (!m || !resource.fullpath) {
55 continue
56 }
57 resource.fullpath = resource.fullpath.replace(
58 '/juju-ui/', '../juju-ui/', 1);
59 }
60 }
61 // Load before test runner
62 mocha.run();
63 });
64 });
65 </script>
6670
67</head>71</head>
6872
6973
=== modified file 'test/test_app.js'
--- test/test_app.js 2012-12-03 20:24:44 +0000
+++ test/test_app.js 2013-01-07 21:41:21 +0000
@@ -119,6 +119,7 @@
119});119});
120120
121YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {121YUI(GlobalConfig).use(['juju-gui', 'juju-tests-utils'], function(Y) {
122
122 describe('Application Connection State', function() {123 describe('Application Connection State', function() {
123 var container;124 var container;
124125
@@ -128,7 +129,7 @@
128129
129 it('should be able to handle env connection status changes', function() {130 it('should be able to handle env connection status changes', function() {
130 var juju = Y.namespace('juju'),131 var juju = Y.namespace('juju'),
131 conn = new(Y.namespace('juju-tests.utils')).SocketStub(),132 conn = new (Y.namespace('juju-tests.utils')).SocketStub(),
132 env = new juju.Environment({conn: conn}),133 env = new juju.Environment({conn: conn}),
133 app = new Y.juju.App({env: env, container: container}),134 app = new Y.juju.App({env: env, container: container}),
134 reset_called = false,135 reset_called = false,
@@ -153,11 +154,14 @@
153 };154 };
154 env.connect();155 env.connect();
155 conn.open();156 conn.open();
157 // We need to fake the connection event.
158 env.set('connected', true);
156 reset_called.should.equal(true);159 reset_called.should.equal(true);
157160
158 // trigger a second time and verify161 // trigger a second time and verify
159 reset_called = false;162 reset_called = false;
160 conn.open();163 conn.open();
164 env.set('connected', true);
161 reset_called.should.equal(true);165 reset_called.should.equal(true);
162 });166 });
163167
@@ -229,6 +233,8 @@
229 env.get_endpoints = function(services, callback) {233 env.get_endpoints = function(services, callback) {
230 get_endpoints_count += 1;234 get_endpoints_count += 1;
231 };235 };
236 // We need to fake the connection event.
237 env.set('connected', true);
232 // Inject default data, should only get_endpoints once.238 // Inject default data, should only get_endpoints once.
233 injectData(app);239 injectData(app);
234 get_endpoints_count.should.equal(1);240 get_endpoints_count.should.equal(1);
235241
=== modified file 'test/test_app_hotkeys.js'
--- test/test_app_hotkeys.js 2012-11-27 14:25:32 +0000
+++ test/test_app_hotkeys.js 2013-01-07 21:41:21 +0000
@@ -9,7 +9,8 @@
9 var env = {9 var env = {
10 after: function() {},10 after: function() {},
11 get: function() {},11 get: function() {},
12 on: function() {}12 on: function() {},
13 set: function() {}
13 };14 };
14 windowNode = Y.one(window);15 windowNode = Y.one(window);
15 app = new Y.juju.App({16 app = new Y.juju.App({
1617
=== added file 'test/test_login.js'
--- test/test_login.js 1970-01-01 00:00:00 +0000
+++ test/test_login.js 2013-01-07 21:41:21 +0000
@@ -0,0 +1,86 @@
1'use strict';
2
3(function() {
4
5 var requires = ['node', 'juju-gui', 'juju-views', 'juju-tests-utils'];
6 var Y = YUI(GlobalConfig).use(requires);
7
8 describe('environment login support', function() {
9 var conn, env, utils, juju, makeLoginView, views, app;
10 var test = it; // We aren't really doing BDD so let's be more direct.
11
12 before(function() {
13 utils = Y.namespace('juju-tests.utils');
14 juju = Y.namespace('juju');
15 });
16
17 beforeEach(function() {
18 var container = Y.Node.create('<div/>');
19 conn = new utils.SocketStub();
20 env = new juju.Environment({conn: conn});
21 env.connect();
22 conn.open();
23 // env.set('connected', true);
24 });
25
26 afterEach(function() {
27 env.destroy();
28 });
29
30 test('the user is initially assumed to be unauthenticated', function() {
31 assert.equal(env.userIsAuthenticated, false);
32 });
33
34 test('successful login event marks user as authenticated', function() {
35 var evt = {data: {op: 'login', result: true}};
36 env.handleLoginEvent(evt);
37 assert.equal(env.userIsAuthenticated, true);
38 });
39
40 test('unsuccessful login event keeps user unauthenticated', function() {
41 var evt = {data: {op: 'login'}};
42 env.handleLoginEvent(evt);
43 assert.equal(env.userIsAuthenticated, false);
44 });
45
46 test('bad credentials are removed', function() {
47 var evt = {data: {op: 'login'}};
48 env.handleLoginEvent(evt);
49 assert.equal(env.get('user'), undefined);
50 assert.equal(env.get('password'), undefined);
51 });
52
53 test('credentials passed to the constructor are stored', function() {
54 var user = 'Will Smith';
55 var password = 'I am legend!';
56 var env = new juju.Environment({
57 user: user,
58 password: password,
59 conn: conn
60 });
61 assert.equal(env.get('user'), user);
62 assert.equal(env.get('password'), password);
63 });
64
65 test('login requests are sent in response to a connection', function() {
66 env.fire('log', {op: 'login', data: {}});
67 });
68
69 test('if already authenticated, login() is a no-op', function() {
70 env.userIsAuthenticated = true;
71 env.login();
72 assert.equal(conn.last_message(), undefined);
73 });
74
75 test('with credentials set, login() sends an RPC message', function() {
76 env.set('user', 'user');
77 env.set('password', 'password');
78 env.login();
79 assert.equal(conn.last_message().op, 'login');
80 assert.equal(conn.last_message().user, 'user');
81 assert.equal(conn.last_message().password, 'password');
82 });
83
84 });
85
86})();
087
=== modified file 'test/test_service_view.js'
--- test/test_service_view.js 2012-11-08 18:16:07 +0000
+++ test/test_service_view.js 2013-01-07 21:41:21 +0000
@@ -23,7 +23,8 @@
23 env = new (Y.namespace('juju')).Environment({conn: conn});23 env = new (Y.namespace('juju')).Environment({conn: conn});
24 env.connect();24 env.connect();
25 conn.open();25 conn.open();
26 container = Y.Node.create('<div id="test-container" />');26 container = Y.Node.create('<div/>')
27 .hide();
27 Y.one('#main').append(container);28 Y.one('#main').append(container);
28 db = new models.Database();29 db = new models.Database();
29 charm = new models.Charm({id: 'cs:precise/mysql-7', description: 'A DB'});30 charm = new models.Charm({id: 'cs:precise/mysql-7', description: 'A DB'});

Subscribers

People subscribed via source and target branches