Merge lp:~benji/juju-gui/login into lp:juju-gui/experimental
- login
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email: mp+141133@code.launchpad.net |
Commit message
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.
Kapil Thangavelu (hazmat) wrote : | # |
Brad Crittenden (bac) wrote : | # |
This looks ok to me, given the changes Kapil requests. I'll look again
after those changes are made.
https:/
File app/app.js (right):
https:/
app/app.js:530: * @param {Object} res ???
Why the duplicate 'res' and the ???
https:/
File app/views/login.js (right):
https:/
app/views/
Why not camelCase?
https:/
app/views/
Why not camelCase?
https:/
app/views/
message to the server.
typo: sending
https:/
File test/test_
https:/
test/test_
not known to be bad (they are
s/know/no/
- 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
Kapil Thangavelu (hazmat) wrote : | # |
On Wed, Jan 2, 2013 at 2:43 PM, <email address hidden> wrote:
> Please take a look.
>
>
>
> https:/
> File app/app.js (right):
>
> https:/
> app/app.js:530: * @param {Object} res ???
> On 2013/01/02 14:45:03, bac wrote:
>
>> Why the duplicate 'res' and the ???
>>
>
> Done.
>
> https:/
> app/app.js:544: if (!view.waiting && !view.userIsAut
> 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:/
> 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:/
> File app/store/env.js (right):
>
> https:/
> env.js#newcode77<https:/
- 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
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:/
File app/store/env.js (right):
https:/
app/store/
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:/
app/store/
The implementation does not do what was specced, but we should look
forward to it anyway, I think.
else {
if (evt.data.
this.
}
if (evt.data.
this.
}
}
In the future we should be able to remove the conditional parts of that,
and rely on the two values existing.
https:/
app/store/
We talked about this and you liked the idea of connecting this to an
event instead (event fired by login attempt).
https:/
File test/index.html (right):
https:/
test/index.html:37: // *after* the test runner is invoked. In which
case the test runner
"In this case,"
Gary Poster (gary) wrote : | # |
Please make a high priority card for the UI implementation, btw
- 312. By Benji York
-
review fixes
Preview Diff
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'}); |
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 henticated) {
app/app.js:544: if (!view.waiting && !view.userIsAut
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 env.js: 77: this.set( 'serverReady' , true);
app/store/
instead of introducing another value just move connected down here.
https:/ /codereview. appspot. com/7007047/