Merge lp:~wallyworld/launchpad/admins-can-unsubscribe-bugs-2 into lp:launchpad
- admins-can-unsubscribe-bugs-2
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Данило Шеган |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13344 |
Proposed branch: | lp:~wallyworld/launchpad/admins-can-unsubscribe-bugs-2 |
Merge into: | lp:launchpad |
Diff against target: |
1175 lines (+107/-914) 9 files modified
lib/canonical/launchpad/windmill/jstests/initialize.js (+0/-164) lib/canonical/launchpad/windmill/jstests/launchpad_ajax.js (+0/-652) lib/canonical/launchpad/windmill/jstests/login.js (+0/-32) lib/lp/bugs/browser/bugsubscription.py (+1/-1) lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+64/-0) lib/lp/bugs/javascript/subscribers_list.js (+2/-39) lib/lp/bugs/javascript/tests/test_subscribers_list.js (+28/-14) lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt (+3/-3) lib/lp/bugs/stories/bugs/bug-add-subscriber.txt (+9/-9) |
To merge this branch: | bzr merge lp:~wallyworld/launchpad/admins-can-unsubscribe-bugs-2 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Данило Шеган (community) | Approve | ||
Review via email: mp+66309@code.launchpad.net |
Commit message
[r=danilo][bug=633,134577] Allow a person to unsubscribe from a bug those people/teams who they have subscribed.
Description of the change
This branch provides the client side code to allow someone to unsubscribe from a bug a person/team they have subscribed. A previous branch implemented the functionality on the model. This branch uses that to provide the unsubscribe link when appropriate.
== Implementation ==
The BugPortletSubsc
The subscribers_list javascript was changed to always add the unsubscribe link when a subscription request is completed successfully.
== Demo and QA ==
Subscribe someone to a bug - the unsubscribe icon should be rendered in the subscription list to allow you to unsubscribe them.
== Tests ==
Added tests to BugPortletSubsc
test_data_
test_data_
Modify the test_subscribeS
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Preview Diff
1 | === removed directory 'lib/canonical/launchpad/windmill' | |||
2 | === removed directory 'lib/canonical/launchpad/windmill/jstests' | |||
3 | === removed file 'lib/canonical/launchpad/windmill/jstests/initialize.js' | |||
4 | --- lib/canonical/launchpad/windmill/jstests/initialize.js 2010-11-16 18:54:59 +0000 | |||
5 | +++ lib/canonical/launchpad/windmill/jstests/initialize.js 1970-01-01 00:00:00 +0000 | |||
6 | @@ -1,164 +0,0 @@ | |||
7 | 1 | /* | ||
8 | 2 | Copyright 2009 Canonical Ltd. This software is licensed under the | ||
9 | 3 | GNU Affero General Public License version 3 (see the file LICENSE). | ||
10 | 4 | |||
11 | 5 | Contains the JS fixtures we use to test AJAX call in windmill. | ||
12 | 6 | |||
13 | 7 | Since AJAX calls will complete asynchronously, we need to use a | ||
14 | 8 | synchronisation mechanism. | ||
15 | 9 | |||
16 | 10 | */ | ||
17 | 11 | |||
18 | 12 | |||
19 | 13 | /* A synchronized test. | ||
20 | 14 | * @param name The name of the test, the DOM node used for synchronization | ||
21 | 15 | * will use that name. | ||
22 | 16 | * | ||
23 | 17 | * @param body This is a list of functions or strings or windmill action | ||
24 | 18 | * object. That makes this test body. | ||
25 | 19 | * | ||
26 | 20 | * @param debug (Optional) If this is set to true, tracing information | ||
27 | 21 | * will be logged on the Firebug console. | ||
28 | 22 | */ | ||
29 | 23 | function SynchronizedTest(name, body, debug) { | ||
30 | 24 | this.name = name; | ||
31 | 25 | this.debug = !!debug; | ||
32 | 26 | this.synced = false; | ||
33 | 27 | |||
34 | 28 | /* Create the test body. */ | ||
35 | 29 | this.test_body = []; | ||
36 | 30 | this.add_windmill_actions(this.test_body, body); | ||
37 | 31 | |||
38 | 32 | /* | ||
39 | 33 | * The tear down is also as an array, so that many cleanup | ||
40 | 34 | * actions can be tacked to it using add_cleanups() | ||
41 | 35 | */ | ||
42 | 36 | // Windmill doesn't like empty array teardown. So we define it as an | ||
43 | 37 | // empty object, which gets replaced by an array when add_cleanups is | ||
44 | 38 | // called. | ||
45 | 39 | //this.teardown = []; | ||
46 | 40 | this.teardown = function () {}; | ||
47 | 41 | } | ||
48 | 42 | |||
49 | 43 | |||
50 | 44 | /* The YUI instance used by SynchronizedTest. */ | ||
51 | 45 | SynchronizedTest.prototype.Y = YUI({ | ||
52 | 46 | bootstrap: false, | ||
53 | 47 | fetchCSS: false, | ||
54 | 48 | combine: false, | ||
55 | 49 | timeout: 50 | ||
56 | 50 | }).use('dump'); | ||
57 | 51 | |||
58 | 52 | /* Create the synchronization node, that should make the wait() caller | ||
59 | 53 | * return. | ||
60 | 54 | * | ||
61 | 55 | * @param result This object will be available from the result attribute. | ||
62 | 56 | * This can be used to provide information passed to the callback | ||
63 | 57 | * and that we want to make assertion on. | ||
64 | 58 | */ | ||
65 | 59 | SynchronizedTest.prototype.sync = function (result) { | ||
66 | 60 | this.log('sync() called with ' + this.Y.dump(result)); | ||
67 | 61 | this.result = result; | ||
68 | 62 | this.synced = true; | ||
69 | 63 | }; | ||
70 | 64 | |||
71 | 65 | |||
72 | 66 | /* Convert a sequence of test items and adds them to a windmill array | ||
73 | 67 | * test specification. | ||
74 | 68 | * | ||
75 | 69 | * That method is used by the constructor and add_cleanups method. | ||
76 | 70 | */ | ||
77 | 71 | SynchronizedTest.prototype.add_windmill_actions = function (list, items) { | ||
78 | 72 | var test = this; | ||
79 | 73 | |||
80 | 74 | /* Windmill invokes all test functions without setting the this parameter. | ||
81 | 75 | * So we create wrapper that will pass it to our functions. | ||
82 | 76 | */ | ||
83 | 77 | function create_test_wrapper (func) { | ||
84 | 78 | return function () { func(test); }; | ||
85 | 79 | } | ||
86 | 80 | |||
87 | 81 | for (var i=0; i< items.length; i++) { | ||
88 | 82 | var test_item = items[i]; | ||
89 | 83 | var yui_lang = this.Y.Lang; | ||
90 | 84 | if (yui_lang.isFunction(test_item)) { | ||
91 | 85 | //Create a wrapper that passes the test as first parameter. | ||
92 | 86 | list.push(create_test_wrapper(test_item)); | ||
93 | 87 | } else if (yui_lang.isString(test_item)) { | ||
94 | 88 | //This calls a method on the test. And sticks the result | ||
95 | 89 | //in the test body. Common use case is to use 'wait_action' to | ||
96 | 90 | //add a windmill wait action for the synchronizing condition. | ||
97 | 91 | var action = test[test_item].call(test); | ||
98 | 92 | list.push(action); | ||
99 | 93 | } else if (yui_lang.isObject(test_item)) { | ||
100 | 94 | //We expect this to be a Windmill action. | ||
101 | 95 | list.push(test_item); | ||
102 | 96 | } else { | ||
103 | 97 | throw new Error( | ||
104 | 98 | 'Unknown test predicate: ' + this.Y.dump(test_item)); | ||
105 | 99 | } | ||
106 | 100 | } | ||
107 | 101 | }; | ||
108 | 102 | |||
109 | 103 | /* Add functions/actions to the "teardown" test. | ||
110 | 104 | * | ||
111 | 105 | * @param cleanups An array of functions, or strings or objects representing | ||
112 | 106 | * windmill actions. (Like in the test body parameter.) | ||
113 | 107 | */ | ||
114 | 108 | SynchronizedTest.prototype.add_cleanups = function (cleanups) { | ||
115 | 109 | if (this.Y.Lang.isFunction(this.teardown)) { | ||
116 | 110 | this.teardown = []; | ||
117 | 111 | } | ||
118 | 112 | this.add_windmill_actions(this.teardown, cleanups); | ||
119 | 113 | }; | ||
120 | 114 | |||
121 | 115 | /* Return a windmill action that can be used to wait for | ||
122 | 116 | * the synchronization to happen. | ||
123 | 117 | */ | ||
124 | 118 | SynchronizedTest.prototype.wait_action = function () { | ||
125 | 119 | var test = this; | ||
126 | 120 | return { | ||
127 | 121 | method: "waits.forJS", | ||
128 | 122 | params: { | ||
129 | 123 | //The function waits for the synced attribute to be set to true; | ||
130 | 124 | //and then resets it so that next synchronization action work. | ||
131 | 125 | js: function () { | ||
132 | 126 | if (test.synced) { | ||
133 | 127 | test.synced = false; | ||
134 | 128 | return true; | ||
135 | 129 | } else { | ||
136 | 130 | return false; | ||
137 | 131 | } | ||
138 | 132 | }, | ||
139 | 133 | timeout: 8000 | ||
140 | 134 | } | ||
141 | 135 | }; | ||
142 | 136 | }; | ||
143 | 137 | |||
144 | 138 | |||
145 | 139 | /* Output a log message when debug is turned on. | ||
146 | 140 | */ | ||
147 | 141 | SynchronizedTest.prototype.log = function (message) { | ||
148 | 142 | if (this.debug && console) { | ||
149 | 143 | console.log(this.name + ': ' + message); | ||
150 | 144 | } | ||
151 | 145 | }; | ||
152 | 146 | |||
153 | 147 | |||
154 | 148 | /* Return a configuration object that can be used as a on | ||
155 | 149 | * specification to YUI.io. | ||
156 | 150 | * | ||
157 | 151 | * It basically will call the test sync method, and save the | ||
158 | 152 | * name of the handler called, and the arguments list. | ||
159 | 153 | */ | ||
160 | 154 | SynchronizedTest.prototype.create_yui_sync_on = function () { | ||
161 | 155 | var test = this; | ||
162 | 156 | return { | ||
163 | 157 | success: function () { | ||
164 | 158 | test.sync({callback: 'success', args: test.Y.Array(arguments)}); | ||
165 | 159 | }, | ||
166 | 160 | failure: function () { | ||
167 | 161 | test.sync({callback: 'failure', args: test.Y.Array(arguments)}); | ||
168 | 162 | } | ||
169 | 163 | }; | ||
170 | 164 | }; | ||
171 | 165 | 0 | ||
172 | === removed file 'lib/canonical/launchpad/windmill/jstests/launchpad_ajax.js' | |||
173 | --- lib/canonical/launchpad/windmill/jstests/launchpad_ajax.js 2011-02-24 22:43:20 +0000 | |||
174 | +++ lib/canonical/launchpad/windmill/jstests/launchpad_ajax.js 1970-01-01 00:00:00 +0000 | |||
175 | @@ -1,652 +0,0 @@ | |||
176 | 1 | // Unit testing framework for the Launchpad AJAX test suite. | ||
177 | 2 | var client = new LP.client.Launchpad(); | ||
178 | 3 | |||
179 | 4 | windmill.jsTest.require('login.js'); | ||
180 | 5 | |||
181 | 6 | |||
182 | 7 | // Test access to prepopulated data. | ||
183 | 8 | test_prepopulated_data_anonymous = new SynchronizedTest( | ||
184 | 9 | 'test_prepopulated_data_anonymous', [ | ||
185 | 10 | {"params": {"url": "/firefox/+bug/1"}, "method": "open"}, | ||
186 | 11 | {"params": {}, "method": "waits.forPageLoad"}, | ||
187 | 12 | // Wait until one of the last element on the page is available. | ||
188 | 13 | {"params": {"xpath": "//div[@id='globalfooter']"}, | ||
189 | 14 | "method": "waits.forElement"}, | ||
190 | 15 | function (test) { | ||
191 | 16 | jum.assertUndefined(LP.links.me); | ||
192 | 17 | jum.assertUndefined(LP.cache.context); | ||
193 | 18 | } | ||
194 | 19 | ]); | ||
195 | 20 | |||
196 | 21 | test_logged_in_as_foo_bar.test_prepopulated_data = new SynchronizedTest( | ||
197 | 22 | 'test_prepopulated_data', [ | ||
198 | 23 | {"params": {"url": "/firefox/+bug/1"}, "method": "open"}, | ||
199 | 24 | {"params": {}, "method": "waits.forPageLoad"}, | ||
200 | 25 | // Wait until one of the last element on the page is available. | ||
201 | 26 | {"params": {"xpath": "//div[@id='globalfooter']"}, | ||
202 | 27 | "method": "waits.forElement"}, | ||
203 | 28 | function (test) { | ||
204 | 29 | // If the user is logged in, the link to their user account | ||
205 | 30 | // will be in the link cache. | ||
206 | 31 | jum.assertEquals(LP.links.me, '/~name16'); | ||
207 | 32 | |||
208 | 33 | // If the view has a context, the context will be in the object | ||
209 | 34 | // cache. | ||
210 | 35 | jum.assertNotUndefined(LP.cache.context); | ||
211 | 36 | var context = LP.cache.context; | ||
212 | 37 | jum.assertNotEquals(context.self_link.indexOf( | ||
213 | 38 | "/api/devel/firefox/+bug/1"), -1); | ||
214 | 39 | jum.assertNotEquals(context.resource_type_link.indexOf( | ||
215 | 40 | "/api/devel/#bug_task"), -1); | ||
216 | 41 | jum.assertNotEquals(context.owner_link.indexOf( | ||
217 | 42 | "/api/devel/~name12"), -1); | ||
218 | 43 | jum.assertNotEquals(context.related_tasks_collection_link.indexOf( | ||
219 | 44 | "/api/devel/firefox/+bug/1/related_tasks"), -1); | ||
220 | 45 | |||
221 | 46 | // Specific views may add additional objects to the object cache | ||
222 | 47 | // or links to the link cache. | ||
223 | 48 | var bug = LP.cache.bug; | ||
224 | 49 | jum.assertNotUndefined(LP.cache.bug); | ||
225 | 50 | jum.assertNotEquals(bug.self_link.indexOf("/api/devel/bugs/1"), -1); | ||
226 | 51 | } | ||
227 | 52 | ]); | ||
228 | 53 | |||
229 | 54 | // Test that making a web service call doesn't modify the callback | ||
230 | 55 | // methods you pass in. | ||
231 | 56 | var test_callback_safety = function() { | ||
232 | 57 | var success_callback = function() {}; | ||
233 | 58 | var config = {on: {success: success_callback}}; | ||
234 | 59 | |||
235 | 60 | // Test a GET. | ||
236 | 61 | client.get("/people", config); | ||
237 | 62 | jum.assertTrue(success_callback === config.on.success); | ||
238 | 63 | |||
239 | 64 | // Test a named POST. | ||
240 | 65 | name = "callback-safety-test" + new Date().getTime(); | ||
241 | 66 | config.parameters = {display_name: 'My new team', name: name}; | ||
242 | 67 | client.named_post("/people", "newTeam", config); | ||
243 | 68 | jum.assertTrue(success_callback === config.on.success); | ||
244 | 69 | }; | ||
245 | 70 | |||
246 | 71 | |||
247 | 72 | var test_normalize_uri = function() { | ||
248 | 73 | var normalize = LP.client.normalize_uri; | ||
249 | 74 | jum.assertEquals(normalize("http://www.example.com/api/devel/foo"), | ||
250 | 75 | "/api/devel/foo"); | ||
251 | 76 | jum.assertEquals(normalize("http://www.example.com/foo/bar"), "/foo/bar"); | ||
252 | 77 | jum.assertEquals(normalize("/foo/bar"), "/api/devel/foo/bar"); | ||
253 | 78 | jum.assertEquals(normalize("/api/devel/foo/bar"), "/api/devel/foo/bar"); | ||
254 | 79 | jum.assertEquals(normalize("foo/bar"), "/api/devel/foo/bar"); | ||
255 | 80 | jum.assertEquals(normalize("api/devel/foo/bar"), "/api/devel/foo/bar"); | ||
256 | 81 | }; | ||
257 | 82 | |||
258 | 83 | var test_append_qs = function() { | ||
259 | 84 | var qs = ""; | ||
260 | 85 | qs = LP.client.append_qs(qs, "Pöllä", "Perelló"); | ||
261 | 86 | jum.assertEquals("P%C3%B6ll%C3%A4=Perell%C3%B3", qs); | ||
262 | 87 | }; | ||
263 | 88 | |||
264 | 89 | var test_field_uri = function() { | ||
265 | 90 | jum.assertEquals(LP.client.get_field_uri("http://www.example.com/api/devel/foo", "field"), | ||
266 | 91 | "/api/devel/foo/field"); | ||
267 | 92 | jum.assertEquals(LP.client.get_field_uri("/no/slash", "field"), | ||
268 | 93 | "/api/devel/no/slash/field"); | ||
269 | 94 | jum.assertEquals(LP.client.get_field_uri("/has/slash/", "field"), | ||
270 | 95 | "/api/devel/has/slash/field"); | ||
271 | 96 | }; | ||
272 | 97 | |||
273 | 98 | // Test that retrieving a non-existent resource uses the failure handler. | ||
274 | 99 | var test_no_such_url = new SynchronizedTest('test_no_such_url', [ | ||
275 | 100 | function (test) { | ||
276 | 101 | client.get("no-such-url", {on: test.create_yui_sync_on()}); | ||
277 | 102 | }, | ||
278 | 103 | 'wait_action', | ||
279 | 104 | function (test) { | ||
280 | 105 | jum.assertEquals('failure', test.result.callback); | ||
281 | 106 | } | ||
282 | 107 | ]); | ||
283 | 108 | |||
284 | 109 | |||
285 | 110 | //Check that retrieving the root by relative URL returns the root entry. | ||
286 | 111 | var test_relative_url = new SynchronizedTest('test_relative_url', [ | ||
287 | 112 | function (test) { | ||
288 | 113 | client.get("", {on: test.create_yui_sync_on()}); | ||
289 | 114 | }, | ||
290 | 115 | 'wait_action', | ||
291 | 116 | function (test) { | ||
292 | 117 | jum.assertEquals('success', test.result.callback); | ||
293 | 118 | var launchpad = test.result.args[0]; | ||
294 | 119 | jum.assertTrue(launchpad instanceof LP.client.Root); | ||
295 | 120 | jum.assertNotNull( | ||
296 | 121 | launchpad.people_collection_link.match(/people$/)); | ||
297 | 122 | } | ||
298 | 123 | ]); | ||
299 | 124 | |||
300 | 125 | |||
301 | 126 | //Check that retrieving the root by absolute URL returns the root entry. | ||
302 | 127 | var test_absolute_url = new SynchronizedTest('test_absolute_url', [ | ||
303 | 128 | function (test) { | ||
304 | 129 | client.get("", {on: test.create_yui_sync_on()}); | ||
305 | 130 | }, | ||
306 | 131 | 'wait_action', | ||
307 | 132 | function (test) { | ||
308 | 133 | jum.assertEquals('success', test.result.callback); | ||
309 | 134 | var launchpad = test.result.args[0]; | ||
310 | 135 | jum.assertTrue(launchpad instanceof LP.client.Root); | ||
311 | 136 | jum.assertNotNull( | ||
312 | 137 | launchpad.people_collection_link.match(/people$/)); | ||
313 | 138 | } | ||
314 | 139 | ]); | ||
315 | 140 | |||
316 | 141 | |||
317 | 142 | // Check that collection resources are paginated. | ||
318 | 143 | var test_pagination_collection = new SynchronizedTest( | ||
319 | 144 | 'test_pagination_collection', [ | ||
320 | 145 | function (test) { | ||
321 | 146 | client.get("/people", {on: test.create_yui_sync_on(), | ||
322 | 147 | start: 2, size: 1}); | ||
323 | 148 | }, | ||
324 | 149 | 'wait_action', | ||
325 | 150 | function (test) { | ||
326 | 151 | jum.assertEquals('success', test.result.callback); | ||
327 | 152 | var people = test.result.args[0]; | ||
328 | 153 | jum.assertEquals(2, people.start); | ||
329 | 154 | jum.assertEquals(1, people.entries.length); | ||
330 | 155 | } | ||
331 | 156 | ]); | ||
332 | 157 | |||
333 | 158 | |||
334 | 159 | // Check invoking a read-only name operation on a resource URI. | ||
335 | 160 | // Make sure it's possible to invoke named operations on URIs. | ||
336 | 161 | var test_named_get = new SynchronizedTest('test_named_get', [ | ||
337 | 162 | function (test) { | ||
338 | 163 | client.named_get( | ||
339 | 164 | "people/", "find", {on: test.create_yui_sync_on(), | ||
340 | 165 | parameters: {text:"salgado"}}); | ||
341 | 166 | }, | ||
342 | 167 | 'wait_action', | ||
343 | 168 | function (test) { | ||
344 | 169 | jum.assertEquals('success', test.result.callback); | ||
345 | 170 | var collection = test.result.args[0]; | ||
346 | 171 | jum.assertTrue(collection instanceof LP.client.Collection); | ||
347 | 172 | jum.assertEquals(1, collection.total_size); | ||
348 | 173 | } | ||
349 | 174 | ]); | ||
350 | 175 | |||
351 | 176 | |||
352 | 177 | |||
353 | 178 | var test_named_get_returning_json = new SynchronizedTest( | ||
354 | 179 | 'test_named_get_returning_json', [ | ||
355 | 180 | // Make sure a named GET that returns a JSON data structure works. | ||
356 | 181 | function (test) { | ||
357 | 182 | client.named_get("ubuntu/+archive/primary", "getBuildCounters", | ||
358 | 183 | {on: test.create_yui_sync_on()}); | ||
359 | 184 | }, | ||
360 | 185 | 'wait_action', | ||
361 | 186 | function (test) { | ||
362 | 187 | jum.assertEquals('success', test.result.callback); | ||
363 | 188 | var structure = test.result.args[0]; | ||
364 | 189 | jum.assertEquals(structure.failed, 5); | ||
365 | 190 | } | ||
366 | 191 | ]); | ||
367 | 192 | |||
368 | 193 | |||
369 | 194 | // Check the invocation of a named POST operation. | ||
370 | 195 | test_logged_in_as_foo_bar.test_named_post = new SynchronizedTest( | ||
371 | 196 | 'test_named_post', [ | ||
372 | 197 | function (test) { | ||
373 | 198 | test.bugtask_uri = '/redfish/+bug/15'; | ||
374 | 199 | client.named_post( | ||
375 | 200 | test.bugtask_uri, 'transitionToStatus', | ||
376 | 201 | {on: test.create_yui_sync_on(), | ||
377 | 202 | parameters: {status: 'Confirmed'}}); | ||
378 | 203 | }, | ||
379 | 204 | 'wait_action', | ||
380 | 205 | function (test) { | ||
381 | 206 | jum.assertEquals('success', test.result.callback); | ||
382 | 207 | test.add_cleanups([ | ||
383 | 208 | function (test) { | ||
384 | 209 | client.named_post( | ||
385 | 210 | test.bugtask_uri, 'transitionToStatus', | ||
386 | 211 | {on: test.create_yui_sync_on(), | ||
387 | 212 | parameters: {'status': 'New'}}); | ||
388 | 213 | }, | ||
389 | 214 | 'wait_action']); | ||
390 | 215 | // Get the bugtask and make sure its status has changed. | ||
391 | 216 | client.get(test.bugtask_uri, {on: test.create_yui_sync_on()}); | ||
392 | 217 | }, | ||
393 | 218 | 'wait_action', | ||
394 | 219 | function(test) { | ||
395 | 220 | jum.assertEquals('success', test.result.callback); | ||
396 | 221 | var bugtask = test.result.args[0]; | ||
397 | 222 | jum.assertEquals(bugtask.get('status'), 'Confirmed'); | ||
398 | 223 | } | ||
399 | 224 | ]); | ||
400 | 225 | |||
401 | 226 | //Test that follow_link return the resource at the end of the link. | ||
402 | 227 | // Retrieve the launchpad root and check the people link is a collection. | ||
403 | 228 | var test_follow_link = new SynchronizedTest('test_follow_link', [ | ||
404 | 229 | function (test) { | ||
405 | 230 | client.get("", {on: test.create_yui_sync_on()}); | ||
406 | 231 | }, | ||
407 | 232 | 'wait_action', | ||
408 | 233 | function (test) { | ||
409 | 234 | jum.assertEquals('success', test.result.callback); | ||
410 | 235 | var root_object = test.result.args[0]; | ||
411 | 236 | root_object.follow_link('people', {on: test.create_yui_sync_on()}); | ||
412 | 237 | }, | ||
413 | 238 | 'wait_action', | ||
414 | 239 | function (test) { | ||
415 | 240 | jum.assertEquals('success', test.result.callback); | ||
416 | 241 | var people = test.result.args[0]; | ||
417 | 242 | jum.assertTrue(people instanceof LP.client.Collection); | ||
418 | 243 | jum.assertEquals(4, people.total_size); | ||
419 | 244 | } | ||
420 | 245 | ]); | ||
421 | 246 | |||
422 | 247 | //Test that follow_link follows through redirect. | ||
423 | 248 | // Retrieve the launchpad root and check the people | ||
424 | 249 | test_logged_in_as_foo_bar.test_follow_link_through_redirect = | ||
425 | 250 | new SynchronizedTest( | ||
426 | 251 | 'test_follow_link_through_redirect', [ | ||
427 | 252 | function (test) { | ||
428 | 253 | client.get("", {on: test.create_yui_sync_on()}); | ||
429 | 254 | }, | ||
430 | 255 | 'wait_action', | ||
431 | 256 | function (test) { | ||
432 | 257 | jum.assertEquals('success', test.result.callback); | ||
433 | 258 | var root_object = test.result.args[0]; | ||
434 | 259 | root_object.follow_link('me', {on: test.create_yui_sync_on()}); | ||
435 | 260 | }, | ||
436 | 261 | 'wait_action', | ||
437 | 262 | function (test) { | ||
438 | 263 | jum.assertEquals('success', test.result.callback); | ||
439 | 264 | var me = test.result.args[0]; | ||
440 | 265 | jum.assertTrue(me instanceof LP.client.Entry); | ||
441 | 266 | jum.assertEquals('name16', me.get('name')); | ||
442 | 267 | } | ||
443 | 268 | ]); | ||
444 | 269 | |||
445 | 270 | |||
446 | 271 | //Test that retrieving an entry resource yield an Entry object. | ||
447 | 272 | var test_entry_get = new SynchronizedTest('test_entry_get', [ | ||
448 | 273 | function (test) { | ||
449 | 274 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
450 | 275 | }, | ||
451 | 276 | 'wait_action', | ||
452 | 277 | function (test) { | ||
453 | 278 | jum.assertEquals('success', test.result.callback); | ||
454 | 279 | var salgado = test.result.args[0]; | ||
455 | 280 | jum.assertTrue(salgado instanceof LP.client.Entry); | ||
456 | 281 | jum.assertEquals("salgado", salgado.get('name')); | ||
457 | 282 | } | ||
458 | 283 | ]); | ||
459 | 284 | |||
460 | 285 | // Test that retrieving an HTML representation of an entry yields an | ||
461 | 286 | // HTML snippet. | ||
462 | 287 | var test_entry_html_get = new SynchronizedTest('test_entry_html_get', [ | ||
463 | 288 | function (test) { | ||
464 | 289 | client.get('~salgado', {on: test.create_yui_sync_on(), | ||
465 | 290 | accept: LP.client.XHTML}); | ||
466 | 291 | }, | ||
467 | 292 | 'wait_action', | ||
468 | 293 | function (test) { | ||
469 | 294 | jum.assertEquals('success', test.result.callback); | ||
470 | 295 | var salgado_html = test.result.args[0]; | ||
471 | 296 | jum.assertNotEquals(salgado_html.indexOf("<dl"), -1); | ||
472 | 297 | } | ||
473 | 298 | ]); | ||
474 | 299 | |||
475 | 300 | |||
476 | 301 | // Test that it's possible to request an HTML representation of | ||
477 | 302 | // an object when updating it. | ||
478 | 303 | test_logged_in_as_foo_bar.test_html_entry_lp_save = new SynchronizedTest( | ||
479 | 304 | 'test_entry_lp_save', [ | ||
480 | 305 | function (test) { | ||
481 | 306 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
482 | 307 | }, | ||
483 | 308 | 'wait_action', | ||
484 | 309 | function (test) { | ||
485 | 310 | jum.assertEquals('success', test.result.callback); | ||
486 | 311 | var salgado = test.result.args[0]; | ||
487 | 312 | salgado.lp_save({on: test.create_yui_sync_on(), | ||
488 | 313 | accept: LP.client.XHTML}); | ||
489 | 314 | }, | ||
490 | 315 | 'wait_action', | ||
491 | 316 | function (test) { | ||
492 | 317 | jum.assertEquals('success', test.result.callback); | ||
493 | 318 | var salgado_html = test.result.args[0]; | ||
494 | 319 | jum.assertNotEquals(salgado_html.indexOf("<dl"), -1); | ||
495 | 320 | |||
496 | 321 | // Now test the patch() method directly. | ||
497 | 322 | client.patch('~salgado', {}, | ||
498 | 323 | {on: test.create_yui_sync_on(), | ||
499 | 324 | accept: LP.client.XHTML}); | ||
500 | 325 | }, | ||
501 | 326 | 'wait_action', | ||
502 | 327 | function (test) { | ||
503 | 328 | jum.assertEquals('success', test.result.callback); | ||
504 | 329 | var salgado_html = test.result.args[0]; | ||
505 | 330 | jum.assertNotEquals(salgado_html.indexOf("<dl"), -1); | ||
506 | 331 | |||
507 | 332 | // Now test the patch() method on a field resource. | ||
508 | 333 | var field_uri = LP.client.get_field_uri('~salgado', 'display_name'); | ||
509 | 334 | client.patch(field_uri, 'Guilherme Salgado 2', | ||
510 | 335 | {on: test.create_yui_sync_on(), | ||
511 | 336 | accept: LP.client.XHTML}); | ||
512 | 337 | }, | ||
513 | 338 | 'wait_action', | ||
514 | 339 | function (test) { | ||
515 | 340 | var field_uri = LP.client.get_field_uri('~salgado', 'display_name'); | ||
516 | 341 | jum.assertEquals('success', test.result.callback); | ||
517 | 342 | var salgado_name_html = test.result.args[0]; | ||
518 | 343 | jum.assertEquals(salgado_name_html, "Guilherme Salgado 2"); | ||
519 | 344 | |||
520 | 345 | // Now make sure patch() on a field resource works when we | ||
521 | 346 | // request a JSON representation in return. | ||
522 | 347 | field_uri = LP.client.get_field_uri('~salgado', 'display_name'); | ||
523 | 348 | client.patch(field_uri, 'Guilherme Salgado', | ||
524 | 349 | {on: test.create_yui_sync_on()}); | ||
525 | 350 | }, | ||
526 | 351 | 'wait_action', | ||
527 | 352 | function (test) { | ||
528 | 353 | var field_uri = LP.client.get_field_uri('~salgado', 'display_name'); | ||
529 | 354 | jum.assertEquals('success', test.result.callback); | ||
530 | 355 | var salgado_name_html = test.result.args[0]; | ||
531 | 356 | jum.assertEquals(salgado_name_html, "Guilherme Salgado"); | ||
532 | 357 | } | ||
533 | 358 | ]); | ||
534 | 359 | |||
535 | 360 | //Test that modifying an entry and then calling lp_save() saves the | ||
536 | 361 | //entry on the server. | ||
537 | 362 | test_logged_in_as_foo_bar.test_entry_lp_save = new SynchronizedTest( | ||
538 | 363 | 'test_entry_lp_save', [ | ||
539 | 364 | function (test) { | ||
540 | 365 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
541 | 366 | }, | ||
542 | 367 | 'wait_action', | ||
543 | 368 | function (test) { | ||
544 | 369 | jum.assertEquals('success', test.result.callback); | ||
545 | 370 | var salgado = test.result.args[0]; | ||
546 | 371 | test.original_display_name = salgado.get('display_name'); | ||
547 | 372 | salgado.set('display_name', '<b>A new display name</b>'); | ||
548 | 373 | salgado.lp_save({on: test.create_yui_sync_on()}); | ||
549 | 374 | }, | ||
550 | 375 | 'wait_action', | ||
551 | 376 | function (test) { | ||
552 | 377 | jum.assertEquals('success', test.result.callback); | ||
553 | 378 | // Make sure that the save operation returned a new version of | ||
554 | 379 | // the object. | ||
555 | 380 | var new_salgado = test.result.args[0]; | ||
556 | 381 | jum.assertEquals(new_salgado.get('display_name'), | ||
557 | 382 | '<b>A new display name</b>'); | ||
558 | 383 | |||
559 | 384 | test.add_cleanups([ | ||
560 | 385 | function (test) { | ||
561 | 386 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
562 | 387 | }, | ||
563 | 388 | 'wait_action', | ||
564 | 389 | function (test) { | ||
565 | 390 | jum.assertEquals('success', test.result.callback); | ||
566 | 391 | var salgado = test.result.args[0]; | ||
567 | 392 | salgado.set('display_name', test.original_display_name); | ||
568 | 393 | salgado.lp_save({on: test.create_yui_sync_on()}); | ||
569 | 394 | jum.assertEquals(salgado.dirty_attributes.length, 0); | ||
570 | 395 | }, | ||
571 | 396 | 'wait_action']); | ||
572 | 397 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
573 | 398 | }, | ||
574 | 399 | 'wait_action', | ||
575 | 400 | function (test) { | ||
576 | 401 | jum.assertEquals('success', test.result.callback); | ||
577 | 402 | var salgado = test.result.args[0]; | ||
578 | 403 | jum.assertEquals('<b>A new display name</b>', | ||
579 | 404 | salgado.get('display_name')); | ||
580 | 405 | |||
581 | 406 | // As long as we've got bad HTML in the display name, let's | ||
582 | 407 | // get an HTML representation and see whether the bad HTML was | ||
583 | 408 | // escaped. | ||
584 | 409 | client.get('~salgado', {on: test.create_yui_sync_on(), | ||
585 | 410 | accept: LP.client.XHTML}); | ||
586 | 411 | }, | ||
587 | 412 | 'wait_action', | ||
588 | 413 | function (test) { | ||
589 | 414 | jum.assertEquals('success', test.result.callback); | ||
590 | 415 | |||
591 | 416 | var salgado_html = test.result.args[0]; | ||
592 | 417 | jum.assertNotEquals(salgado_html.indexOf("<dl"), -1); | ||
593 | 418 | jum.assertNotEquals(salgado_html.indexOf("<b>A new"), -1); | ||
594 | 419 | |||
595 | 420 | // Now test the patch() method directly. | ||
596 | 421 | client.patch('~salgado', {'display_name': 'A patched display name'}, | ||
597 | 422 | {on: test.create_yui_sync_on()}); | ||
598 | 423 | }, | ||
599 | 424 | 'wait_action', | ||
600 | 425 | function (test) { | ||
601 | 426 | jum.assertEquals('success', test.result.callback); | ||
602 | 427 | client.get('~salgado', {on: test.create_yui_sync_on()}); | ||
603 | 428 | }, | ||
604 | 429 | 'wait_action', | ||
605 | 430 | function (test) { | ||
606 | 431 | jum.assertEquals('success', test.result.callback); | ||
607 | 432 | var salgado = test.result.args[0]; | ||
608 | 433 | jum.assertEquals('A patched display name', salgado.get('display_name')); | ||
609 | 434 | |||
610 | 435 | // Test that a mismatched ETag results in a failed save. | ||
611 | 436 | salgado.set('http_etag', "Non-matching ETag."); | ||
612 | 437 | salgado.set('display_name', "This display name will not be set."); | ||
613 | 438 | salgado.lp_save({on: test.create_yui_sync_on()}); | ||
614 | 439 | }, | ||
615 | 440 | 'wait_action', | ||
616 | 441 | function (test) { | ||
617 | 442 | jum.assertEquals('failure', test.result.callback); | ||
618 | 443 | var xhr = test.result.args[1]; | ||
619 | 444 | jum.assertEquals(xhr.status, 412); | ||
620 | 445 | } | ||
621 | 446 | ]); | ||
622 | 447 | |||
623 | 448 | |||
624 | 449 | //Test retrieving a collection object. | ||
625 | 450 | var test_collection = new SynchronizedTest('test_collection', [ | ||
626 | 451 | function (test) { | ||
627 | 452 | client.get("people", {on: test.create_yui_sync_on()}); | ||
628 | 453 | }, | ||
629 | 454 | 'wait_action', | ||
630 | 455 | function (test) { | ||
631 | 456 | jum.assertEquals('success', test.result.callback); | ||
632 | 457 | var collection = test.result.args[0]; | ||
633 | 458 | jum.assertTrue(collection instanceof LP.client.Collection); | ||
634 | 459 | jum.assertEquals(4, collection.total_size); | ||
635 | 460 | }, | ||
636 | 461 | 'wait_action', | ||
637 | 462 | function (test) { | ||
638 | 463 | jum.assertEquals('success', test.result.callback); | ||
639 | 464 | var entries = test.result.args[0].entries, | ||
640 | 465 | length = test.result.args[0].total_size, | ||
641 | 466 | index; | ||
642 | 467 | for (index = 0 ; index < length ; index++) { | ||
643 | 468 | jum.assertTrue(entries[index] instanceof LP.client.Entry); | ||
644 | 469 | } | ||
645 | 470 | }, | ||
646 | 471 | 'wait_action', | ||
647 | 472 | function (test) { | ||
648 | 473 | jum.assertEquals('success', test.result.callback); | ||
649 | 474 | var entry = test.result.args[0].entries[0]; | ||
650 | 475 | jum.assertEquals('test', entry.display_name); | ||
651 | 476 | entry.set('display_name', "Set Display Name"); | ||
652 | 477 | entry.lp_save({on: test.create_yui_sync_on()}); | ||
653 | 478 | }, | ||
654 | 479 | 'wait_action', | ||
655 | 480 | function (test) { | ||
656 | 481 | jum.assertEquals('success', test.result.callback); | ||
657 | 482 | client.get('people', { on: test.create_yui_sync_on()}); | ||
658 | 483 | }, | ||
659 | 484 | 'wait_action', | ||
660 | 485 | function (test) { | ||
661 | 486 | jum.assertEquals('success', test.result.callback); | ||
662 | 487 | var entry = test.result.args[0].entries[0]; | ||
663 | 488 | jum.assertEquals('Set Display Name', entry.display_name); | ||
664 | 489 | } | ||
665 | 490 | ]); | ||
666 | 491 | |||
667 | 492 | |||
668 | 493 | //Test the lp_slice() method on a collection. | ||
669 | 494 | var test_collection_lp_slice = new SynchronizedTest( | ||
670 | 495 | 'test_collection_lp_slice', [ | ||
671 | 496 | function (test) { | ||
672 | 497 | client.get("people", {on: test.create_yui_sync_on()}); | ||
673 | 498 | }, | ||
674 | 499 | 'wait_action', | ||
675 | 500 | function (test) { | ||
676 | 501 | jum.assertEquals('success', test.result.callback); | ||
677 | 502 | var collection = test.result.args[0]; | ||
678 | 503 | collection.lp_slice(test.create_yui_sync_on(), 2, 1); | ||
679 | 504 | }, | ||
680 | 505 | 'wait_action', | ||
681 | 506 | function (test) { | ||
682 | 507 | jum.assertEquals('success', test.result.callback); | ||
683 | 508 | var slice = test.result.args[0]; | ||
684 | 509 | jum.assertEquals(2, slice.start); | ||
685 | 510 | jum.assertEquals(1, slice.entries.length); | ||
686 | 511 | } | ||
687 | 512 | ]); | ||
688 | 513 | |||
689 | 514 | |||
690 | 515 | //Test invoking a named GET on a collection. | ||
691 | 516 | var test_collection_named_get = new SynchronizedTest( | ||
692 | 517 | 'test_collection_named_get', [ | ||
693 | 518 | function (test) { | ||
694 | 519 | client.get("people", {on: test.create_yui_sync_on() }); | ||
695 | 520 | }, | ||
696 | 521 | 'wait_action', | ||
697 | 522 | function (test) { | ||
698 | 523 | jum.assertEquals('success', test.result.callback); | ||
699 | 524 | var collection = test.result.args[0]; | ||
700 | 525 | collection.named_get( | ||
701 | 526 | 'find', {on: test.create_yui_sync_on(), | ||
702 | 527 | parameters: {text: 'salgado'}}); | ||
703 | 528 | }, | ||
704 | 529 | 'wait_action', | ||
705 | 530 | function (test) { | ||
706 | 531 | jum.assertEquals('success', test.result.callback); | ||
707 | 532 | var collection = test.result.args[0]; | ||
708 | 533 | jum.assertTrue(collection instanceof LP.client.Collection); | ||
709 | 534 | jum.assertEquals(1, collection.total_size); | ||
710 | 535 | } | ||
711 | 536 | ]); | ||
712 | 537 | |||
713 | 538 | |||
714 | 539 | //Test named POST on a collection, and object creation. | ||
715 | 540 | test_logged_in_as_foo_bar.test_collection_named_post = new SynchronizedTest( | ||
716 | 541 | 'test_collection_named_post', [ | ||
717 | 542 | function(test) { | ||
718 | 543 | client.get("/people/", {on: test.create_yui_sync_on()}); | ||
719 | 544 | }, | ||
720 | 545 | 'wait_action', | ||
721 | 546 | function(test) { | ||
722 | 547 | // Generate a unique team name so that the team-creation test | ||
723 | 548 | // can be run multiple times without resetting the dataset. | ||
724 | 549 | name = "newteam" + new Date().getTime(); | ||
725 | 550 | var collection = test.result.args[0]; | ||
726 | 551 | collection.named_post('newTeam', | ||
727 | 552 | {on: test.create_yui_sync_on(), | ||
728 | 553 | parameters: {display_name: 'My new team', | ||
729 | 554 | name: name}}); | ||
730 | 555 | }, | ||
731 | 556 | 'wait_action', | ||
732 | 557 | function(test) { | ||
733 | 558 | var new_entry = test.result.args[0]; | ||
734 | 559 | jum.assertEquals("success", test.result.callback); | ||
735 | 560 | jum.assertTrue(new_entry instanceof LP.client.Entry); | ||
736 | 561 | jum.assertEquals(new_entry.get("display_name"), "My new team"); | ||
737 | 562 | jum.assertNotEquals(new_entry.lp_original_uri.indexOf("/~newteam"), | ||
738 | 563 | -1); | ||
739 | 564 | } | ||
740 | 565 | ]); | ||
741 | 566 | |||
742 | 567 | //Test paging on a named collection. | ||
743 | 568 | var test_collection_paged_named_get = new SynchronizedTest( | ||
744 | 569 | 'test_collection_paged_named_get', [ | ||
745 | 570 | function (test) { | ||
746 | 571 | client.get("people", {on: test.create_yui_sync_on() }); | ||
747 | 572 | }, | ||
748 | 573 | 'wait_action', | ||
749 | 574 | function (test) { | ||
750 | 575 | jum.assertEquals('success', test.result.callback); | ||
751 | 576 | var collection = test.result.args[0]; | ||
752 | 577 | collection.named_get( | ||
753 | 578 | 'find', {on: test.create_yui_sync_on(), | ||
754 | 579 | parameters: {text: 'salgado'}, | ||
755 | 580 | start: 10}); | ||
756 | 581 | }, | ||
757 | 582 | 'wait_action', | ||
758 | 583 | function (test) { | ||
759 | 584 | jum.assertEquals('success', test.result.callback); | ||
760 | 585 | var collection = test.result.args[0]; | ||
761 | 586 | jum.assertTrue(collection instanceof LP.client.Collection); | ||
762 | 587 | jum.assertEquals(1, collection.total_size); | ||
763 | 588 | } | ||
764 | 589 | ]); | ||
765 | 590 | |||
766 | 591 | // Test hosted file objects. | ||
767 | 592 | |||
768 | 593 | // To test PUT to a hosted file we need to create a brand new | ||
769 | 594 | // file. Several problems combine to make this necessary. The | ||
770 | 595 | // first is that you can't send a binary file through XHR: it gets | ||
771 | 596 | // truncated at the first null character. So we can't just PUT to | ||
772 | 597 | // a mugshot or icon. There are some product release files in the | ||
773 | 598 | // preexisting dataset, but they don't have anything backing them | ||
774 | 599 | // in the librarian, so we can't get a proper handle on them. So | ||
775 | 600 | // we need to create a brand new file and then test PUT on it. | ||
776 | 601 | |||
777 | 602 | // Unfortunately, currently there are no files you can create with | ||
778 | 603 | // PUT, so we can't test this. | ||
779 | 604 | |||
780 | 605 | test_logged_in_as_foo_bar.test_hosted_files = new SynchronizedTest( | ||
781 | 606 | |||
782 | 607 | 'test_hosted_files', [ | ||
783 | 608 | function(test) { | ||
784 | 609 | var bug_uri = '/bugs/15'; | ||
785 | 610 | client.named_post(bug_uri, 'addAttachment', | ||
786 | 611 | {on: test.create_yui_sync_on(), | ||
787 | 612 | parameters: {comment: 'A new attachment', | ||
788 | 613 | content_type: 'text/plain', | ||
789 | 614 | data: 'Some data.', | ||
790 | 615 | filename: 'foo.txt'}}); | ||
791 | 616 | }, | ||
792 | 617 | 'wait_action', | ||
793 | 618 | function(test) { | ||
794 | 619 | jum.assertEquals('success', test.result.callback); | ||
795 | 620 | var attachment = test.result.args[0]; | ||
796 | 621 | attachment.follow_link('data', | ||
797 | 622 | {on: test.create_yui_sync_on()}); | ||
798 | 623 | }, | ||
799 | 624 | 'wait_action', | ||
800 | 625 | function(test) { | ||
801 | 626 | jum.assertEquals('success', test.result.callback); | ||
802 | 627 | var hosted_file = test.result.args[0]; | ||
803 | 628 | |||
804 | 629 | // Unfortunately, there's no hosted file that can be edited through | ||
805 | 630 | // the web service, so we can't test PUT. | ||
806 | 631 | // hosted_file.contents = "['Unit tester was here.']"; | ||
807 | 632 | // hosted_file.filename = "unittest.json"; | ||
808 | 633 | // hosted_file.content_type = "application/json"; | ||
809 | 634 | // hosted_file.lp_save({on: test.create_yui_sync_on()}); | ||
810 | 635 | // }, | ||
811 | 636 | // 'wait_action', | ||
812 | 637 | // function(test) { | ||
813 | 638 | // jum.assertEquals('success', test.result.callback); | ||
814 | 639 | // var hosted_file = test.result.args[2]; | ||
815 | 640 | hosted_file.lp_delete({on: test.create_yui_sync_on()}); | ||
816 | 641 | }, | ||
817 | 642 | 'wait_action', | ||
818 | 643 | function(test) { | ||
819 | 644 | jum.assertEquals('failure', test.result.callback); | ||
820 | 645 | // XXX flacoste 2008/12/12 bug=307539 | ||
821 | 646 | // This code works right now, but when testing a hosted file | ||
822 | 647 | // that can be edited through the web service, it will fail. | ||
823 | 648 | var request = test.result.args[1]; | ||
824 | 649 | jum.assertEquals(405, request.status); | ||
825 | 650 | } | ||
826 | 651 | ] | ||
827 | 652 | ); | ||
828 | 653 | 0 | ||
829 | === removed file 'lib/canonical/launchpad/windmill/jstests/login.js' | |||
830 | --- lib/canonical/launchpad/windmill/jstests/login.js 2009-06-30 21:06:27 +0000 | |||
831 | +++ lib/canonical/launchpad/windmill/jstests/login.js 1970-01-01 00:00:00 +0000 | |||
832 | @@ -1,32 +0,0 @@ | |||
833 | 1 | /* | ||
834 | 2 | Copyright 2009 Canonical Ltd. This software is licensed under the | ||
835 | 3 | GNU Affero General Public License version 3 (see the file LICENSE). | ||
836 | 4 | |||
837 | 5 | Namespaces for tests requiring to be logged in. | ||
838 | 6 | */ | ||
839 | 7 | |||
840 | 8 | |||
841 | 9 | /* Logged in as Foo Bar. */ | ||
842 | 10 | var test_logged_in_as_foo_bar = {}; | ||
843 | 11 | test_logged_in_as_foo_bar.setup = [ | ||
844 | 12 | {"params": {"link": "Log in \/ Register"}, | ||
845 | 13 | "method": "asserts.assertNode"}, | ||
846 | 14 | {"params": {"link": "Log in \/ Register"}, "method": "click"}, | ||
847 | 15 | {"params": {}, "method": "waits.forPageLoad"}, | ||
848 | 16 | {"params": {"id": "email"}, "method": "waits.forElement"}, | ||
849 | 17 | {"params": {"text": "foo.bar@canonical.com", "id": "email"}, | ||
850 | 18 | "method": "type"}, | ||
851 | 19 | {"params": {"text": "test", "id": "password"}, "method": "type"}, | ||
852 | 20 | {"params": {"name": "loginpage_submit_login"}, "method": "click"}, | ||
853 | 21 | {"params": {}, "method": "waits.forPageLoad"}, | ||
854 | 22 | {"params": {"link": "Foo Bar"}, "method": "waits.forElement"} | ||
855 | 23 | ]; | ||
856 | 24 | |||
857 | 25 | test_logged_in_as_foo_bar.teardown = [ | ||
858 | 26 | {"params": {"name": "logout"}, "method": "click"}, | ||
859 | 27 | // We need the waits.forPageLoad here because it's likely that the | ||
860 | 28 | // xpath expression might match on the previous page. | ||
861 | 29 | {"params": {}, "method": "waits.forPageLoad"}, | ||
862 | 30 | {"params": {"xpath": "\/html\/body[@id='document']\/div[@id='mainarea']\/div[@id='container']\/div"}, "method": "waits.forElement"}, | ||
863 | 31 | {"params": {"xpath": "\/html\/body[@id='document']\/div[@id='mainarea']\/div[@id='container']\/div", "validator": "You have been logged out"}, "method": "asserts.assertText"} | ||
864 | 32 | ]; | ||
865 | 33 | 0 | ||
866 | === modified file 'lib/lp/bugs/browser/bugsubscription.py' | |||
867 | --- lib/lp/bugs/browser/bugsubscription.py 2011-06-17 10:44:18 +0000 | |||
868 | +++ lib/lp/bugs/browser/bugsubscription.py 2011-06-30 22:44:42 +0000 | |||
869 | @@ -540,7 +540,7 @@ | |||
870 | 540 | details = list(bug.getDirectSubscribersWithDetails()) | 540 | details = list(bug.getDirectSubscribersWithDetails()) |
871 | 541 | api_request = IWebServiceClientRequest(self.request) | 541 | api_request = IWebServiceClientRequest(self.request) |
872 | 542 | for person, subscription in details: | 542 | for person, subscription in details: |
874 | 543 | can_edit = self.user is not None and self.user.inTeam(person) | 543 | can_edit = subscription.canBeUnsubscribedByUser(self.user) |
875 | 544 | if person == self.user or (person.private and not can_edit): | 544 | if person == self.user or (person.private and not can_edit): |
876 | 545 | # Skip the current user viewing the page, | 545 | # Skip the current user viewing the page, |
877 | 546 | # and private teams user is not a member of. | 546 | # and private teams user is not a member of. |
878 | 547 | 547 | ||
879 | === modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py' | |||
880 | --- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-17 10:31:55 +0000 | |||
881 | +++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-06-30 22:44:42 +0000 | |||
882 | @@ -7,6 +7,7 @@ | |||
883 | 7 | 7 | ||
884 | 8 | from simplejson import dumps | 8 | from simplejson import dumps |
885 | 9 | 9 | ||
886 | 10 | from zope.component import getUtility | ||
887 | 10 | from zope.traversing.browser import absoluteURL | 11 | from zope.traversing.browser import absoluteURL |
888 | 11 | 12 | ||
889 | 12 | from canonical.launchpad.ftests import LaunchpadFormHarness | 13 | from canonical.launchpad.ftests import LaunchpadFormHarness |
890 | @@ -19,10 +20,12 @@ | |||
891 | 19 | BugSubscriptionSubscribeSelfView, | 20 | BugSubscriptionSubscribeSelfView, |
892 | 20 | ) | 21 | ) |
893 | 21 | from lp.bugs.enum import BugNotificationLevel | 22 | from lp.bugs.enum import BugNotificationLevel |
894 | 23 | from lp.registry.interfaces.person import IPersonSet | ||
895 | 22 | from lp.testing import ( | 24 | from lp.testing import ( |
896 | 23 | person_logged_in, | 25 | person_logged_in, |
897 | 24 | TestCaseWithFactory, | 26 | TestCaseWithFactory, |
898 | 25 | ) | 27 | ) |
899 | 28 | from lp.testing.sampledata import ADMIN_EMAIL | ||
900 | 26 | from lp.testing.views import create_initialized_view | 29 | from lp.testing.views import create_initialized_view |
901 | 27 | 30 | ||
902 | 28 | 31 | ||
903 | @@ -573,6 +576,67 @@ | |||
904 | 573 | self.assertEqual( | 576 | self.assertEqual( |
905 | 574 | dumps([expected_result]), harness.view.subscriber_data_js) | 577 | dumps([expected_result]), harness.view.subscriber_data_js) |
906 | 575 | 578 | ||
907 | 579 | def test_data_subscription_lp_admin(self): | ||
908 | 580 | # For a subscription, subscriber_data_js has can_edit | ||
909 | 581 | # set to true for a Launchpad admin. | ||
910 | 582 | bug = self._makeBugWithNoSubscribers() | ||
911 | 583 | member = self.factory.makePerson() | ||
912 | 584 | subscriber = self.factory.makePerson( | ||
913 | 585 | name='user', displayname='Subscriber Name') | ||
914 | 586 | with person_logged_in(member): | ||
915 | 587 | bug.subscribe(subscriber, subscriber, | ||
916 | 588 | level=BugNotificationLevel.LIFECYCLE) | ||
917 | 589 | harness = LaunchpadFormHarness( | ||
918 | 590 | bug, BugPortletSubscribersWithDetails) | ||
919 | 591 | api_request = IWebServiceClientRequest(harness.request) | ||
920 | 592 | |||
921 | 593 | expected_result = { | ||
922 | 594 | 'subscriber': { | ||
923 | 595 | 'name': 'user', | ||
924 | 596 | 'display_name': 'Subscriber Name', | ||
925 | 597 | 'is_team': False, | ||
926 | 598 | 'can_edit': True, | ||
927 | 599 | 'web_link': canonical_url(subscriber), | ||
928 | 600 | 'self_link': absoluteURL(subscriber, api_request), | ||
929 | 601 | }, | ||
930 | 602 | 'subscription_level': "Lifecycle", | ||
931 | 603 | } | ||
932 | 604 | |||
933 | 605 | # Login as admin | ||
934 | 606 | admin = getUtility(IPersonSet).find(ADMIN_EMAIL).any() | ||
935 | 607 | with person_logged_in(admin): | ||
936 | 608 | self.assertEqual( | ||
937 | 609 | dumps([expected_result]), harness.view.subscriber_data_js) | ||
938 | 610 | |||
939 | 611 | def test_data_person_subscription_subscriber(self): | ||
940 | 612 | # For a subscription, subscriber_data_js has can_edit | ||
941 | 613 | # set to true for the subscriber. | ||
942 | 614 | bug = self._makeBugWithNoSubscribers() | ||
943 | 615 | subscriber = self.factory.makePerson( | ||
944 | 616 | name='user', displayname='Subscriber Name') | ||
945 | 617 | subscribed_by = self.factory.makePerson( | ||
946 | 618 | name='someone', displayname='Subscribed By Name') | ||
947 | 619 | with person_logged_in(subscriber): | ||
948 | 620 | bug.subscribe(subscriber, subscribed_by, | ||
949 | 621 | level=BugNotificationLevel.LIFECYCLE) | ||
950 | 622 | harness = LaunchpadFormHarness(bug, BugPortletSubscribersWithDetails) | ||
951 | 623 | api_request = IWebServiceClientRequest(harness.request) | ||
952 | 624 | |||
953 | 625 | expected_result = { | ||
954 | 626 | 'subscriber': { | ||
955 | 627 | 'name': 'user', | ||
956 | 628 | 'display_name': 'Subscriber Name', | ||
957 | 629 | 'is_team': False, | ||
958 | 630 | 'can_edit': True, | ||
959 | 631 | 'web_link': canonical_url(subscriber), | ||
960 | 632 | 'self_link': absoluteURL(subscriber, api_request), | ||
961 | 633 | }, | ||
962 | 634 | 'subscription_level': "Lifecycle", | ||
963 | 635 | } | ||
964 | 636 | with person_logged_in(subscribed_by): | ||
965 | 637 | self.assertEqual( | ||
966 | 638 | dumps([expected_result]), harness.view.subscriber_data_js) | ||
967 | 639 | |||
968 | 576 | def test_data_person_subscription_user_excluded(self): | 640 | def test_data_person_subscription_user_excluded(self): |
969 | 577 | # With the subscriber logged in, he is not included in the results. | 641 | # With the subscriber logged in, he is not included in the results. |
970 | 578 | bug = self._makeBugWithNoSubscribers() | 642 | bug = self._makeBugWithNoSubscribers() |
971 | 579 | 643 | ||
972 | === modified file 'lib/lp/bugs/javascript/subscribers_list.js' | |||
973 | --- lib/lp/bugs/javascript/subscribers_list.js 2011-06-27 14:23:58 +0000 | |||
974 | +++ lib/lp/bugs/javascript/subscribers_list.js 2011-06-30 22:44:42 +0000 | |||
975 | @@ -325,7 +325,8 @@ | |||
976 | 325 | 325 | ||
977 | 326 | function on_success() { | 326 | function on_success() { |
978 | 327 | loader.subscribers_list.stopSubscriberActivity(subscriber, true); | 327 | loader.subscribers_list.stopSubscriberActivity(subscriber, true); |
980 | 328 | loader._addUnsubscribeLinkIfTeamMember(subscriber); | 328 | loader.subscribers_list.addUnsubscribeAction( |
981 | 329 | subscriber, loader._getUnsubscribeCallback()); | ||
982 | 329 | } | 330 | } |
983 | 330 | function on_failure(t_id, response) { | 331 | function on_failure(t_id, response) { |
984 | 331 | loader.subscribers_list.stopSubscriberActivity( | 332 | loader.subscribers_list.stopSubscriberActivity( |
985 | @@ -347,44 +348,6 @@ | |||
986 | 347 | }; | 348 | }; |
987 | 348 | 349 | ||
988 | 349 | /** | 350 | /** |
989 | 350 | * Add unsubscribe link for a team if the currently logged in user | ||
990 | 351 | * is member of the team. | ||
991 | 352 | * | ||
992 | 353 | * @method _addUnsubscribeLinkIfTeamMember | ||
993 | 354 | * @param team {Object} A person object as returned via API. | ||
994 | 355 | */ | ||
995 | 356 | BugSubscribersLoader.prototype | ||
996 | 357 | ._addUnsubscribeLinkIfTeamMember = function(team) { | ||
997 | 358 | var loader = this; | ||
998 | 359 | function on_success(members) { | ||
999 | 360 | var team_member = false; | ||
1000 | 361 | var i; | ||
1001 | 362 | for (i=0; i<members.entries.length; i++) { | ||
1002 | 363 | if (members.entries[i].get('member_link') === | ||
1003 | 364 | Y.lp.client.get_absolute_uri(LP.links.me)) { | ||
1004 | 365 | team_member = true; | ||
1005 | 366 | break; | ||
1006 | 367 | } | ||
1007 | 368 | } | ||
1008 | 369 | if (team_member === true) { | ||
1009 | 370 | // Add unsubscribe action for the team member. | ||
1010 | 371 | loader.subscribers_list.addUnsubscribeAction( | ||
1011 | 372 | team, loader._getUnsubscribeCallback()); | ||
1012 | 373 | } | ||
1013 | 374 | } | ||
1014 | 375 | |||
1015 | 376 | if (Y.Lang.isString(LP.links.me) && team.is_team) { | ||
1016 | 377 | var config = { | ||
1017 | 378 | on: { success: on_success } | ||
1018 | 379 | }; | ||
1019 | 380 | |||
1020 | 381 | var members_link = team.members_details_collection_link; | ||
1021 | 382 | this.lp_client.get(members_link, config); | ||
1022 | 383 | } | ||
1023 | 384 | }; | ||
1024 | 385 | |||
1025 | 386 | |||
1026 | 387 | /** | ||
1027 | 388 | * Manages entire subscribers' list for a single bug. | 351 | * Manages entire subscribers' list for a single bug. |
1028 | 389 | * | 352 | * |
1029 | 390 | * If the passed in container_box is not present, or if there are multiple | 353 | * If the passed in container_box is not present, or if there are multiple |
1030 | 391 | 354 | ||
1031 | === modified file 'lib/lp/bugs/javascript/tests/test_subscribers_list.js' | |||
1032 | --- lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-28 16:26:34 +0000 | |||
1033 | +++ lib/lp/bugs/javascript/tests/test_subscribers_list.js 2011-06-30 22:44:42 +0000 | |||
1034 | @@ -2002,8 +2002,8 @@ | |||
1035 | 2002 | 2002 | ||
1036 | 2003 | test_subscribeSomeoneElse_success: function() { | 2003 | test_subscribeSomeoneElse_success: function() { |
1037 | 2004 | // When subscribing someone else succeeds, stopSubscriberActivity | 2004 | // When subscribing someone else succeeds, stopSubscriberActivity |
1040 | 2005 | // is called indicating success, and _addUnsubscribeLinkIfTeamMember | 2005 | // is called indicating success, and addUnsubscribeAction |
1041 | 2006 | // is called to add unsubscribe-link when needed. | 2006 | // is called with the correct parameters. |
1042 | 2007 | 2007 | ||
1043 | 2008 | var subscriber = { name: "user", self_link: "/~user" }; | 2008 | var subscriber = { name: "user", self_link: "/~user" }; |
1044 | 2009 | 2009 | ||
1045 | @@ -2011,17 +2011,29 @@ | |||
1046 | 2011 | var loader = setUpLoader(this.root); | 2011 | var loader = setUpLoader(this.root); |
1047 | 2012 | loader.subscribers_list.addSubscriber(subscriber, "Maybe"); | 2012 | loader.subscribers_list.addSubscriber(subscriber, "Maybe"); |
1048 | 2013 | 2013 | ||
1050 | 2014 | // Mock-up addUnsubscribeLinkIfTeamMember method to | 2014 | // Mock-up addUnsubscribeAction method to |
1051 | 2015 | // ensure it's called with the right parameters. | 2015 | // ensure it's called with the right parameters. |
1059 | 2016 | var subscriber_link_added = false; | 2016 | // We need to stub the _getUnsubscribeCallback result so we can check |
1060 | 2017 | var old_addLink = | 2017 | // the unsubscribe_callback. |
1061 | 2018 | module.BugSubscribersLoader | 2018 | var unsubscribe_callback = function() {}; |
1062 | 2019 | .prototype._addUnsubscribeLinkIfTeamMember; | 2019 | // Save old methods for restoring later. |
1063 | 2020 | module.BugSubscribersLoader.prototype | 2020 | var old_getUnsub = module.BugSubscribersLoader.prototype |
1064 | 2021 | ._addUnsubscribeLinkIfTeamMember = | 2021 | ._getUnsubscribeCallback; |
1065 | 2022 | function(my_subscriber) { | 2022 | |
1066 | 2023 | // Make _getUnsubscribeCallback return the new callback. | ||
1067 | 2024 | module.BugSubscribersLoader.prototype._getUnsubscribeCallback = | ||
1068 | 2025 | function() { | ||
1069 | 2026 | return unsubscribe_callback; | ||
1070 | 2027 | }; | ||
1071 | 2028 | |||
1072 | 2029 | var unsubscribe_link_added = false; | ||
1073 | 2030 | var old_unsubscribe_action = | ||
1074 | 2031 | module.SubscribersList.prototype.addUnsubscribeAction; | ||
1075 | 2032 | module.SubscribersList.prototype.addUnsubscribeAction = | ||
1076 | 2033 | function(my_subscriber, callback) { | ||
1077 | 2023 | Y.Assert.areSame(subscriber, my_subscriber); | 2034 | Y.Assert.areSame(subscriber, my_subscriber); |
1079 | 2024 | subscriber_link_added = true; | 2035 | Y.Assert.areEqual(unsubscribe_callback, callback); |
1080 | 2036 | unsubscribe_link_added = true; | ||
1081 | 2025 | }; | 2037 | }; |
1082 | 2026 | 2038 | ||
1083 | 2027 | // Mock-up stopSubscriberActivity to ensure it's called. | 2039 | // Mock-up stopSubscriberActivity to ensure it's called. |
1084 | @@ -2051,12 +2063,14 @@ | |||
1085 | 2051 | 2063 | ||
1086 | 2052 | loader._subscribeSomeoneElse(person); | 2064 | loader._subscribeSomeoneElse(person); |
1087 | 2053 | 2065 | ||
1089 | 2054 | Y.Assert.isTrue(subscriber_link_added); | 2066 | Y.Assert.isTrue(unsubscribe_link_added); |
1090 | 2055 | Y.Assert.isFalse(activity_on); | 2067 | Y.Assert.isFalse(activity_on); |
1091 | 2056 | 2068 | ||
1092 | 2057 | // Restore original methods. | 2069 | // Restore original methods. |
1095 | 2058 | module.BugSubscribersLoader.prototype.addUnsubscribeLinkIfTeamMember = | 2070 | module.SubscribersList.prototype.addUnsubscribeAction = |
1096 | 2059 | old_addLink; | 2071 | old_unsubscribe_action; |
1097 | 2072 | module.BugSubscribersLoader.prototype._getUnsubscribeCallback = | ||
1098 | 2073 | old_getUnsub; | ||
1099 | 2060 | module.SubscribersList.prototype.stopSubscriberActivity = | 2074 | module.SubscribersList.prototype.stopSubscriberActivity = |
1100 | 2061 | old_indicate; | 2075 | old_indicate; |
1101 | 2062 | 2076 | ||
1102 | 2063 | 2077 | ||
1103 | === modified file 'lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt' | |||
1104 | --- lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt 2011-06-16 13:50:58 +0000 | |||
1105 | +++ lib/lp/bugs/stories/bug-privacy/05-set-bug-private-as-admin.txt 2011-06-30 22:44:42 +0000 | |||
1106 | @@ -15,7 +15,7 @@ | |||
1107 | 15 | ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details") | 15 | ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details") |
1108 | 16 | 16 | ||
1109 | 17 | >>> print_direct_subscribers(browser.contents) | 17 | >>> print_direct_subscribers(browser.contents) |
1111 | 18 | Steve Alexander | 18 | Steve Alexander (Unsubscribe) |
1112 | 19 | >>> print_also_notified(browser.contents) | 19 | >>> print_also_notified(browser.contents) |
1113 | 20 | Also notified: | 20 | Also notified: |
1114 | 21 | Sample Person | 21 | Sample Person |
1115 | @@ -38,8 +38,8 @@ | |||
1116 | 38 | >>> browser.open( | 38 | >>> browser.open( |
1117 | 39 | ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details") | 39 | ... "http://launchpad.dev/bugs/2/+bug-portlet-subscribers-details") |
1118 | 40 | >>> print_direct_subscribers(browser.contents) | 40 | >>> print_direct_subscribers(browser.contents) |
1121 | 41 | Sample Person | 41 | Sample Person (Unsubscribe) |
1122 | 42 | Steve Alexander | 42 | Steve Alexander (Unsubscribe) |
1123 | 43 | Ubuntu Team (Unsubscribe) | 43 | Ubuntu Team (Unsubscribe) |
1124 | 44 | 44 | ||
1125 | 45 | >>> print_also_notified(browser.contents) | 45 | >>> print_also_notified(browser.contents) |
1126 | 46 | 46 | ||
1127 | === modified file 'lib/lp/bugs/stories/bugs/bug-add-subscriber.txt' | |||
1128 | --- lib/lp/bugs/stories/bugs/bug-add-subscriber.txt 2011-06-16 13:50:58 +0000 | |||
1129 | +++ lib/lp/bugs/stories/bugs/bug-add-subscriber.txt 2011-06-30 22:44:42 +0000 | |||
1130 | @@ -67,7 +67,7 @@ | |||
1131 | 67 | ... 'http://bugs.launchpad.dev/bugs/1/' | 67 | ... 'http://bugs.launchpad.dev/bugs/1/' |
1132 | 68 | ... '+bug-portlet-subscribers-details') | 68 | ... '+bug-portlet-subscribers-details') |
1133 | 69 | >>> print_direct_subscribers(user_browser.contents) | 69 | >>> print_direct_subscribers(user_browser.contents) |
1135 | 70 | David Allouche | 70 | David Allouche (Unsubscribe) |
1136 | 71 | Sample Person | 71 | Sample Person |
1137 | 72 | Steve Alexander | 72 | Steve Alexander |
1138 | 73 | 73 | ||
1139 | @@ -110,8 +110,8 @@ | |||
1140 | 110 | ... 'http://bugs.launchpad.dev/bugs/1/' | 110 | ... 'http://bugs.launchpad.dev/bugs/1/' |
1141 | 111 | ... '+bug-portlet-subscribers-details') | 111 | ... '+bug-portlet-subscribers-details') |
1142 | 112 | >>> print_direct_subscribers(user_browser.contents) | 112 | >>> print_direct_subscribers(user_browser.contents) |
1145 | 113 | David Allouche | 113 | David Allouche (Unsubscribe) |
1146 | 114 | Landscape Developers | 114 | Landscape Developers (Unsubscribe) |
1147 | 115 | Sample Person | 115 | Sample Person |
1148 | 116 | Steve Alexander | 116 | Steve Alexander |
1149 | 117 | 117 | ||
1150 | @@ -144,11 +144,11 @@ | |||
1151 | 144 | ... 'http://bugs.launchpad.dev/bugs/1/' | 144 | ... 'http://bugs.launchpad.dev/bugs/1/' |
1152 | 145 | ... '+bug-portlet-subscribers-details') | 145 | ... '+bug-portlet-subscribers-details') |
1153 | 146 | >>> print_direct_subscribers(foobar_browser.contents) | 146 | >>> print_direct_subscribers(foobar_browser.contents) |
1156 | 147 | David Allouche | 147 | David Allouche (Unsubscribe) |
1157 | 148 | Landscape Developers | 148 | Landscape Developers (Unsubscribe) |
1158 | 149 | Private Team (Unsubscribe) | 149 | Private Team (Unsubscribe) |
1161 | 150 | Sample Person | 150 | Sample Person (Unsubscribe) |
1162 | 151 | Steve Alexander | 151 | Steve Alexander (Unsubscribe) |
1163 | 152 | 152 | ||
1164 | 153 | Someone not in the team will not see the private team in the | 153 | Someone not in the team will not see the private team in the |
1165 | 154 | subscribers list. | 154 | subscribers list. |
1166 | @@ -157,8 +157,8 @@ | |||
1167 | 157 | ... 'http://bugs.launchpad.dev/bugs/1/' | 157 | ... 'http://bugs.launchpad.dev/bugs/1/' |
1168 | 158 | ... '+bug-portlet-subscribers-details') | 158 | ... '+bug-portlet-subscribers-details') |
1169 | 159 | >>> print_direct_subscribers(user_browser.contents) | 159 | >>> print_direct_subscribers(user_browser.contents) |
1172 | 160 | David Allouche | 160 | David Allouche (Unsubscribe) |
1173 | 161 | Landscape Developers | 161 | Landscape Developers (Unsubscribe) |
1174 | 162 | Sample Person | 162 | Sample Person |
1175 | 163 | Steve Alexander | 163 | Steve Alexander |
1176 | 164 | 164 |
Looks great, Ian, thanks for fixing this!