Merge lp:~bac/launchpad/bug-745660 into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: 12717
Proposed branch: lp:~bac/launchpad/bug-745660
Merge into: lp:launchpad
Diff against target: 764 lines (+145/-518)
5 files modified
.bzrignore (+1/-0)
Makefile (+17/-12)
lib/canonical/launchpad/icing/icon-sprites.positioning (+0/-484)
lib/lp/bugs/browser/structuralsubscription.py (+13/-3)
lib/lp/bugs/browser/tests/test_expose.py (+114/-19)
To merge this branch: bzr merge lp:~bac/launchpad/bug-745660
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+55636@code.launchpad.net

Commit message

[r=allenap][bug=745660] Filter the list of administered teams shown for structural subscriptions to distributions when a bug supervisor is set.

Description of the change

= Summary =

If a distribution has a bug supervisor team set then only members of
that team my have a structural subscription to that distro. This branch
filters out those teams from the ones presented in the structural
subscription widget.

== Proposed fix ==

The set of teams exposed to JavaScript is filtered under the conditions
stated above.

== Pre-implementation notes ==

Chats with Gary about the problem.

== Implementation details ==

As above.

== Tests ==

bin/test -vvm lp.bugs -t test_expose

== Demo and Q/A ==

Go to https://launchpad.dev/+feature-rules and set the following rule:
malone.advanced-structural-subscriptions.enabled default 1 on

Login as stevea/test.

Go to https://launchpad.dev/ubuntu and click on 'Subscribe to bug mail'.
 Note that 'Ubuntu Gnome Team' is in the list of teams Steve can subscribe.

Go to http://bugs.launchpad.dev/ubuntu and set the Ubuntu Team as the
bug supervisor. You'll note that Ubuntu Gnome Team is not a member of
Ubuntu Team.

Go back to https://launchpad.dev/ubuntu and click on 'Subscribe to bug
mail'. Now you'll see that Ubuntu Gnome Team is not one of his
selections. QED.

= Launchpad lint =

(Will fix as appropriate.)

Checking for conflicts and issues in changed files.

Linting changed files:
  .bzrignore
  lib/lp/bugs/browser/structuralsubscription.py
  lib/lp/bugs/browser/tests/test_expose.py
  Makefile

./lib/lp/bugs/browser/tests/test_expose.py
     111: E231 missing whitespace after ','
./Makefile
      81: Line exceeds 78 characters.
     159: Line exceeds 78 characters.
     289: Line exceeds 78 characters.
     416: Line exceeds 78 characters.
     446: Line exceeds 78 characters.
     480: Line exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

This branch also removes icon-sprites.positioning from our tree as it is generated. The Makefile rules are updated to ensure it is built when needed.

Revision history for this message
Gavin Panella (allenap) wrote :

Cool, +1.

[1]

+${ICING}/icon-sprites.positioning: bin/sprite-util
+ ${SHHH} bin/sprite-util create-image
+
+${ICING}/icon-sprites: bin/sprite-util
  ${SHHH} bin/sprite-util create-image

Although make seems to see that the command is identical and only runs
it once, it might be clearer for future maintainers to express it
instead as:

${ICING}/icon-sprites.positioning ${ICING}/icon-sprites: bin/sprite-util
 ${SHHH} bin/sprite-util create-image

[2]

+ def _sort(self, team_info, key='title'):
+ return sorted(team_info, cmp=lambda a,b: cmp(a[key], b[key]))

The cmp argument to sorted it deprecated and is gone in Python 3. This
could be expressed instead as:

        return sorted(team_info, key=lambda item: item[key])

or:

        return sorted(team_info, key=itemgetter(key))

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-03-30 19:42:37 +0000
3+++ .bzrignore 2011-03-31 15:37:43 +0000
4@@ -80,3 +80,4 @@
5 .idea
6 run.gdb
7 lib/canonical/launchpad/icing/icon-sprites
8+lib/canonical/launchpad/icing/icon-sprites.positioning
9
10=== modified file 'Makefile'
11--- Makefile 2011-03-30 19:42:37 +0000
12+++ Makefile 2011-03-31 15:37:43 +0000
13@@ -78,7 +78,8 @@
14 $(API_INDEX): $(BZR_VERSION_INFO) $(PY)
15 rm -rf $(APIDOC_DIR) $(APIDOC_DIR).tmp
16 mkdir -p $(APIDOC_DIR).tmp
17- LPCONFIG=$(LPCONFIG) $(PY) ./utilities/create-lp-wadl-and-apidoc.py --force "$(WADL_TEMPLATE)"
18+ LPCONFIG=$(LPCONFIG) $(PY) ./utilities/create-lp-wadl-and-apidoc.py \
19+ --force "$(WADL_TEMPLATE)"
20 mv $(APIDOC_DIR).tmp $(APIDOC_DIR)
21
22 apidoc: compile $(API_INDEX)
23@@ -156,12 +157,13 @@
24
25 sprite_css: ${LP_BUILT_JS_ROOT}/sprite.css
26
27-${LP_BUILT_JS_ROOT}/sprite.css: bin/sprite-util ${ICING}/sprite.css.in ${ICING}/icon-sprites.positioning
28+${LP_BUILT_JS_ROOT}/sprite.css: bin/sprite-util ${ICING}/sprite.css.in \
29+ ${ICING}/icon-sprites.positioning
30 ${SHHH} bin/sprite-util create-css
31
32-sprite_image: ${ICING}/icon-sprites
33+sprite_image: ${ICING}/icon-sprites ${ICING}/icon-sprites.positioning
34
35-${ICING}/icon-sprites: bin/sprite-util ${ICING}/icon-sprites.positioning
36+${ICING}/icon-sprites.positioning ${ICING}/icon-sprites: bin/sprite-util
37 ${SHHH} bin/sprite-util create-image
38
39 # We absolutely do not want to include the lazr.testing module and
40@@ -410,7 +412,9 @@
41 /var/tmp/testkeyserver
42 # /var/tmp/launchpad_mailqueue is created read-only on ec2test
43 # instances.
44- if [ -w /var/tmp/launchpad_mailqueue ]; then $(RM) -rf /var/tmp/launchpad_mailqueue; fi
45+ if [ -w /var/tmp/launchpad_mailqueue ]; then \
46+ $(RM) -rf /var/tmp/launchpad_mailqueue; \
47+ fi
48
49
50 realclean: clean
51@@ -474,10 +478,11 @@
52 --docformat restructuredtext --verbose-about epytext-summary \
53 $(PYDOCTOR_OPTIONS)
54
55-.PHONY: apidoc buildout_bin check doc tags TAGS zcmldocs realclean clean debug \
56- stop start run ftest_build ftest_inplace test_build test_inplace \
57- pagetests check schema default launchpad.pot pull_branches \
58- scan_branches sync_branches reload-apache hosted_branches \
59- check_mailman check_config jsbuild jsbuild_lazr clean_js \
60- clean_buildout buildonce_eggs build_eggs sprite_css sprite_image \
61- css_combine compile check_schema pydoctor clean_logs \
62+.PHONY: apidoc buildout_bin check doc tags TAGS zcmldocs realclean \
63+ clean debug stop start run ftest_build ftest_inplace \
64+ test_build test_inplace pagetests check schema default \
65+ launchpad.pot pull_branches scan_branches sync_branches \
66+ reload-apache hosted_branches check_mailman check_config \
67+ jsbuild jsbuild_lazr clean_js clean_buildout buildonce_eggs \
68+ build_eggs sprite_css sprite_image css_combine compile \
69+ check_schema pydoctor clean_logs
70
71=== removed file 'lib/canonical/launchpad/icing/icon-sprites.positioning'
72--- lib/canonical/launchpad/icing/icon-sprites.positioning 2011-03-22 05:56:34 +0000
73+++ lib/canonical/launchpad/icing/icon-sprites.positioning 1970-01-01 00:00:00 +0000
74@@ -1,484 +0,0 @@
75-/* DO NOT EDIT THIS FILE BY HAND!!! */
76-/* It is autogenerated by spriteutils. */
77-{
78- "../images/arrowLeft.png": [
79- 0,
80- -14918
81- ],
82- "../images/cancel.png": [
83- 0,
84- -7540
85- ],
86- "../images/milestone.png": [
87- 0,
88- -3440
89- ],
90- "../images/build-needed.png": [
91- 0,
92- -13442
93- ],
94- "../images/team.png": [
95- 0,
96- -2130
97- ],
98- "../images/bug-undecided.png": [
99- 0,
100- -8036
101- ],
102- "../images/blueprint-low.png": [
103- 0,
104- -5900
105- ],
106- "../images/meeting.png": [
107- 0,
108- -10490
109- ],
110- "../images/no.png": [
111- 0,
112- -1312
113- ],
114- "../images/distribution-badge.png": [
115- 0,
116- -9184
117- ],
118- "../images/arrowTop.png": [
119- 0,
120- -14590
121- ],
122- "../images/zoom-in.png": [
123- 0,
124- -11802
125- ],
126- "../images/team-badge.png": [
127- 0,
128- -2294
129- ],
130- "../images/blue-bar.png": [
131- 0,
132- -15082
133- ],
134- "../images/arrowStart.png": [
135- 0,
136- -14262
137- ],
138- "../images/ppa-icon-inactive.png": [
139- 0,
140- -12458
141- ],
142- "../images/zoom-out.png": [
143- 0,
144- -11966
145- ],
146- "../images/purple-bar.png": [
147- 0,
148- -15410
149- ],
150- "../images/bullet.png": [
151- 0,
152- -11638
153- ],
154- "../images/info-large.png": [
155- 0,
156- -17504
157- ],
158- "../images/trash-logo.png": [
159- 0,
160- -20340
161- ],
162- "../images/warning.png": [
163- 0,
164- -10162
165- ],
166- "../images/mail.png": [
167- 0,
168- -3932
169- ],
170- "../images/build-failure.png": [
171- 0,
172- -13606
173- ],
174- "../images/branch-large.png": [
175- 0,
176- -16230
177- ],
178- "../images/download-large.png": [
179- 0,
180- -17322
181- ],
182- "../images/private-large.png": [
183- 0,
184- -18232
185- ],
186- "../images/launchpad-large.png": [
187- 0,
188- -17686
189- ],
190- "../images/translation-file.png": [
191- 0,
192- -10818
193- ],
194- "../images/source-package-recipe.png": [
195- 0,
196- -12622
197- ],
198- "../images/project-logo.png": [
199- 0,
200- -18842
201- ],
202- "../images/bug-medium.png": [
203- 0,
204- -4752
205- ],
206- "../images/architecture.png": [
207- 0,
208- -12130
209- ],
210- "../images/tour-icon": [
211- 0,
212- -16066
213- ],
214- "../images/trash-icon.png": [
215- 0,
216- -11146
217- ],
218- "../images/person-inactive.png": [
219- 0,
220- -6722
221- ],
222- "../images/arrowBottom.png": [
223- 0,
224- -14754
225- ],
226- "../images/project.png": [
227- 0,
228- -9508
229- ],
230- "../images/crowd.png": [
231- 0,
232- -1640
233- ],
234- "../images/info.png": [
235- 0,
236- -492
237- ],
238- "../images/flame-icon.png": [
239- 0,
240- -7872
241- ],
242- "../images/ubuntu-icon.png": [
243- 0,
244- -6556
245- ],
246- "../images/link.png": [
247- 0,
248- -3768
249- ],
250- "../images/person-logo.png": [
251- 0,
252- -19270
253- ],
254- "../images/distribution-logo.png": [
255- 0,
256- -18628
257- ],
258- "../images/retry.png": [
259- 0,
260- -9020
261- ],
262- "../images/rss.png": [
263- 0,
264- -6392
265- ],
266- "../images/private.png": [
267- 0,
268- -10326
269- ],
270- "../images/merge-proposal-icon.png": [
271- 0,
272- -12950
273- ],
274- "../images/download.png": [
275- 0,
276- -984
277- ],
278- "../images/arrowDown.png": [
279- 0,
280- -14098
281- ],
282- "../images/package-binary.png": [
283- 0,
284- -8856
285- ],
286- "../images/maybe.png": [
287- 0,
288- -7212
289- ],
290- "../images/bug-status-expand.png": [
291- 0,
292- -12786
293- ],
294- "../images/crowd-large.png": [
295- 0,
296- -16594
297- ],
298- "../images/blueprint.png": [
299- 0,
300- -5244
301- ],
302- "../images/project-badge.png": [
303- 0,
304- -9346
305- ],
306- "../images/bug-high.png": [
307- 0,
308- -4588
309- ],
310- "../images/blueprint-undefined.png": [
311- 0,
312- -6064
313- ],
314- "../images/blueprint-not.png": [
315- 0,
316- -6228
317- ],
318- "../images/stop.png": [
319- 0,
320- -11310
321- ],
322- "../images/flame-large.png": [
323- 0,
324- -17140
325- ],
326- "../images/bug-dupe-icon.png": [
327- 0,
328- -8528
329- ],
330- "../images/bug-critical.png": [
331- 0,
332- -4424
333- ],
334- "../images/build-success.png": [
335- 0,
336- -13278
337- ],
338- "../images/haspatch-icon.png": [
339- 0,
340- -15902
341- ],
342- "../images/person-inactive-badge.png": [
343- 0,
344- -6886
345- ],
346- "../images/ppa-icon.png": [
347- 0,
348- -12294
349- ],
350- "../images/yes.png": [
351- 0,
352- -1476
353- ],
354- "../images/team-logo.png": [
355- 0,
356- -19698
357- ],
358- "../images/arrowRight.png": [
359- 0,
360- -2456
361- ],
362- "../images/blueprint-high.png": [
363- 0,
364- -5572
365- ],
366- "../images/product.png": [
367- 0,
368- -9834
369- ],
370- "../images/bug-low.png": [
371- 0,
372- -4916
373- ],
374- "../images/package-source.png": [
375- 0,
376- -3276
377- ],
378- "../images/language.png": [
379- 0,
380- -3604
381- ],
382- "../images/person.png": [
383- 0,
384- -1804
385- ],
386- "../images/arrowUp.png": [
387- 0,
388- -13934
389- ],
390- "../images/distribution.png": [
391- 0,
392- -3112
393- ],
394- "../images/error-large.png": [
395- 0,
396- -16958
397- ],
398- "../images/news.png": [
399- 0,
400- -15738
401- ],
402- "../images/treeExpanded.png": [
403- 0,
404- -2784
405- ],
406- "../images/build-depwait.png": [
407- 0,
408- -13770
409- ],
410- "../images/blueprint-essential.png": [
411- 0,
412- -5408
413- ],
414- "../images/question.png": [
415- 0,
416- -656
417- ],
418- "../images/error.png": [
419- 0,
420- -7376
421- ],
422- "../images/bug-unknown.png": [
423- 0,
424- -8364
425- ],
426- "../images/product-logo.png": [
427- 0,
428- -19056
429- ],
430- "../images/blueprint-medium.png": [
431- 0,
432- -5736
433- ],
434- "../images/product-badge.png": [
435- 0,
436- -9672
437- ],
438- "../images/list.png": [
439- 0,
440- -11474
441- ],
442- "../images/launchpad-logo.png": [
443- 0,
444- -18414
445- ],
446- "../images/flame-logo.png": [
447- 0,
448- -20126
449- ],
450- "../images/translation-template.png": [
451- 0,
452- -10982
453- ],
454- "../images/bugtracker-icon.png": [
455- 0,
456- -8692
457- ],
458- "../images/meeting-logo.png": [
459- 0,
460- -19912
461- ],
462- "../images/treeCollapsed.png": [
463- 0,
464- -2620
465- ],
466- "../images/green-bar.png": [
467- 0,
468- -15246
469- ],
470- "../images/build-superseded.png": [
471- 0,
472- -13114
473- ],
474- "../images/trash-large.png": [
475- 0,
476- -18050
477- ],
478- "../images/red-bar.png": [
479- 0,
480- -15574
481- ],
482- "../images/add.png": [
483- 0,
484- 0
485- ],
486- "../images/remove.png": [
487- 0,
488- -328
489- ],
490- "../images/read-only.png": [
491- 0,
492- -9998
493- ],
494- "../images/person-inactive-logo.png": [
495- 0,
496- -19484
497- ],
498- "../images/edit.png": [
499- 0,
500- -164
501- ],
502- "../images/bug-wishlist.png": [
503- 0,
504- -5080
505- ],
506- "../images/warning-large.png": [
507- 0,
508- -16412
509- ],
510- "../images/arrowEnd.png": [
511- 0,
512- -14426
513- ],
514- "../images/cve.png": [
515- 0,
516- -4096
517- ],
518- "../images/notification-close.png": [
519- 0,
520- -20725
521- ],
522- "../images/merge-proposal-large.png": [
523- 0,
524- -17868
525- ],
526- "../images/branch.png": [
527- 0,
528- -2948
529- ],
530- "../images/person-badge.png": [
531- 0,
532- -1968
533- ],
534- "../images/notification-private.png": [
535- 0,
536- -20554
537- ],
538- "../images/bug.png": [
539- 0,
540- -4260
541- ],
542- "../images/bug-remote.png": [
543- 0,
544- -8200
545- ],
546- "../images/translation.png": [
547- 0,
548- -10654
549- ],
550- "../images/confirm.png": [
551- 0,
552- -7706
553- ],
554- "../images/search.png": [
555- 0,
556- -1148
557- ]
558-}
559\ No newline at end of file
560
561=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
562--- lib/lp/bugs/browser/structuralsubscription.py 2011-03-29 22:34:04 +0000
563+++ lib/lp/bugs/browser/structuralsubscription.py 2011-03-31 15:37:43 +0000
564@@ -58,6 +58,9 @@
565 IStructuralSubscriptionForm,
566 IStructuralSubscriptionTarget,
567 )
568+from lp.registry.interfaces.distribution import (
569+ IDistribution,
570+ )
571 from lp.registry.interfaces.distributionsourcepackage import (
572 IDistributionSourcePackage,
573 )
574@@ -376,7 +379,7 @@
575 def expose_structural_subscription_data_to_js(context, request,
576 user, subscriptions=None):
577 """Expose all of the data for a structural subscription to JavaScript."""
578- expose_user_administered_teams_to_js(request, user)
579+ expose_user_administered_teams_to_js(request, user, context)
580 expose_enum_to_js(request, BugTaskImportance, 'importances')
581 expose_enum_to_js(request, BugTaskStatus, 'statuses')
582 if subscriptions is None:
583@@ -393,13 +396,20 @@
584 IJSONRequestCache(request).objects[name] = info
585
586
587-def expose_user_administered_teams_to_js(request, user,
588+def expose_user_administered_teams_to_js(request, user, context,
589 absoluteURL=absoluteURL):
590- """Make the list of teams the user adminsters available to JavaScript."""
591+ """Make the list of teams the user administers available to JavaScript."""
592 info = []
593 api_request = IWebServiceClientRequest(request)
594+ is_distro = IDistribution.providedBy(context)
595 if user is not None:
596 for team in user.getAdministratedTeams():
597+ # If the context is a distro AND a bug supervisor is set AND
598+ # the admininistered team is not a member of the bug supervisor
599+ # team THEN skip it.
600+ if (is_distro and context.bug_supervisor is not None and
601+ not team.inTeam(context.bug_supervisor)):
602+ continue
603 info.append({
604 'link': absoluteURL(team, api_request),
605 'title': team.title,
606
607=== modified file 'lib/lp/bugs/browser/tests/test_expose.py'
608--- lib/lp/bugs/browser/tests/test_expose.py 2011-03-24 15:31:53 +0000
609+++ lib/lp/bugs/browser/tests/test_expose.py 2011-03-31 15:37:43 +0000
610@@ -3,6 +3,8 @@
611
612 """Tests for helpers that expose data about a user to on-page JavaScript."""
613
614+from operator import itemgetter
615+
616 from lazr.enum import (
617 DBEnumeratedType,
618 DBItem,
619@@ -86,33 +88,126 @@
620 return self.return_value
621
622
623-class TestStructuralSubscriptionHelpers(TestCase):
624- """Test the helpers used to add data that the on-page JS can use."""
625-
626- def test_teams(self):
627+class TestExposeAdministeredTeams(TestCaseWithFactory):
628+ """Test the function to expose administered team."""
629+
630+ layer = DatabaseFunctionalLayer
631+
632+ def setUp(self):
633+ super(TestExposeAdministeredTeams, self).setUp()
634+ self.request = FakeRequest()
635+ self.user = self.factory.makePerson()
636+
637+ def _setup_teams(self, owner):
638+ self.bug_super_team = self.factory.makeTeam(
639+ name='bug-supervisor-team', owner=owner)
640+ bug_super_subteam = self.factory.makeTeam(
641+ name='bug-supervisor-sub-team', owner=owner)
642+ self.factory.makeTeam(
643+ name='unrelated-team', owner=owner)
644+ with person_logged_in(owner):
645+ bug_super_subteam.join(
646+ self.bug_super_team, self.bug_super_team.teamowner)
647+
648+ def _sort(self, team_info, key='title'):
649+ return sorted(team_info, key=itemgetter(key))
650+
651+ def test_teams_for_non_distro(self):
652 # The expose_user_administered_teams_to_js function loads some data
653 # about the teams the requesting user administers into the response to
654 # be made available to JavaScript.
655
656- request = FakeRequest()
657- user = FakeUser()
658- expose_user_administered_teams_to_js(request, user,
659+ context = self.factory.makeProduct(owner=self.user)
660+ self._setup_teams(self.user)
661+
662+ expose_user_administered_teams_to_js(self.request, self.user, context,
663 absoluteURL=fake_absoluteURL)
664
665 # The team information should have been added to the request.
666- self.assertThat(request.objects, Contains('administratedTeams'))
667- team_info = request.objects['administratedTeams']
668- # Since there are two (fake) teams, there should be two items in the
669+ self.assertThat(self.request.objects, Contains('administratedTeams'))
670+ team_info = self._sort(self.request.objects['administratedTeams'])
671+ # Since there are three teams, there should be three items in the
672 # list of team info.
673- self.assertThat(len(team_info), Equals(2))
674- # The items info consist of a dictionary with link and title keys.
675- self.assertThat(team_info[0], KeysEqual('link', 'title'))
676- self.assertThat(team_info[1], KeysEqual('link', 'title'))
677- # The link is the title of the team.
678- self.assertThat(team_info[0]['title'], Equals('Team One'))
679- # The link is the API link to the team.
680- self.assertThat(team_info[0]['link'],
681- Equals('http://example.com/TeamOne'))
682+ expected_number_teams = 3
683+ self.assertThat(len(team_info), Equals(expected_number_teams))
684+ # The items info consist of a dictionary with link and title keys.
685+ for i in range(expected_number_teams):
686+ self.assertThat(team_info[i], KeysEqual('link', 'title'))
687+ # The link is the title of the team.
688+ self.assertThat(
689+ team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
690+ self.assertThat(
691+ team_info[1]['title'], Equals(u'Bug Supervisor Team'))
692+ self.assertThat(
693+ team_info[2]['title'], Equals(u'Unrelated Team'))
694+ # The link is the API link to the team.
695+ self.assertThat(team_info[0]['link'],
696+ Equals('http://example.com/BugSupervisorSubTeam'))
697+
698+ def test_teams_for_distro_with_bug_super(self):
699+ self._setup_teams(self.user)
700+ context = self.factory.makeDistribution(
701+ owner=self.user, members=self.bug_super_team)
702+ with person_logged_in(self.user):
703+ context.setBugSupervisor(
704+ self.bug_super_team, self.user)
705+
706+ expose_user_administered_teams_to_js(self.request, self.user, context,
707+ absoluteURL=fake_absoluteURL)
708+
709+ # The team information should have been added to the request.
710+ self.assertThat(self.request.objects, Contains('administratedTeams'))
711+ team_info = self._sort(self.request.objects['administratedTeams'])
712+
713+ # Since the distro only returns teams that are members of the bug
714+ # supervisor team, we only expect two.
715+ expected_number_teams = 2
716+ self.assertThat(len(team_info), Equals(expected_number_teams))
717+ # The items info consist of a dictionary with link and title keys.
718+ for i in range(expected_number_teams):
719+ self.assertThat(team_info[i], KeysEqual('link', 'title'))
720+ # The link is the title of the team.
721+ self.assertThat(
722+ team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
723+ self.assertThat(
724+ team_info[1]['title'], Equals(u'Bug Supervisor Team'))
725+ # The link is the API link to the team.
726+ self.assertThat(team_info[0]['link'],
727+ Equals('http://example.com/BugSupervisorSubTeam'))
728+
729+ def test_teams_for_distro_with_no_bug_super(self):
730+ self._setup_teams(self.user)
731+ context = self.factory.makeDistribution(
732+ owner=self.user, members=self.bug_super_team)
733+
734+ expose_user_administered_teams_to_js(self.request, self.user, context,
735+ absoluteURL=fake_absoluteURL)
736+
737+ # The team information should have been added to the request.
738+ self.assertThat(self.request.objects, Contains('administratedTeams'))
739+ team_info = self._sort(self.request.objects['administratedTeams'])
740+
741+ # Since the distro has no bug supervisor set, all administered teams
742+ # are returned.
743+ expected_number_teams = 3
744+ self.assertThat(len(team_info), Equals(expected_number_teams))
745+ # The items info consist of a dictionary with link and title keys.
746+ for i in range(expected_number_teams):
747+ self.assertThat(team_info[i], KeysEqual('link', 'title'))
748+ # The link is the title of the team.
749+ self.assertThat(
750+ team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
751+ self.assertThat(
752+ team_info[1]['title'], Equals(u'Bug Supervisor Team'))
753+ self.assertThat(
754+ team_info[2]['title'], Equals(u'Unrelated Team'))
755+ # The link is the API link to the team.
756+ self.assertThat(team_info[0]['link'],
757+ Equals('http://example.com/BugSupervisorSubTeam'))
758+
759+
760+class TestStructuralSubscriptionHelpers(TestCase):
761+ """Test the helpers used to add data that the on-page JS can use."""
762
763 def test_expose_enum_to_js(self):
764 # Loads the titles of an enum into the response.