~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/browser/tests/test_bugtask.py

[rs=buildbot-poller] automatic merge from stable. Revisions: 14253,
        14254, 14255, 14256, 14257, 14258, 14259 included.

Show diffs side-by-side

added added

removed removed

Lines of Context:
6
6
from contextlib import contextmanager
7
7
from datetime import datetime
8
8
import re
 
9
import simplejson
9
10
import urllib
10
11
 
11
12
from lazr.lifecycle.event import ObjectModifiedEvent
36
37
    )
37
38
from canonical.launchpad.testing.pages import find_tag_by_id
38
39
from canonical.launchpad.webapp import canonical_url
 
40
from canonical.launchpad.webapp.authorization import clear_cache
 
41
from canonical.launchpad.webapp.interfaces import (
 
42
    ILaunchBag,
 
43
    ILaunchpadRoot,
 
44
    )
39
45
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
40
46
from canonical.testing.layers import (
41
47
    DatabaseFunctionalLayer,
82
88
from lp.testing.views import create_initialized_view
83
89
 
84
90
 
 
91
DELETE_BUGTASK_ENABLED = {u"disclosure.delete_bugtask.enabled": u"on"}
 
92
 
 
93
 
85
94
class TestBugTaskView(TestCaseWithFactory):
86
95
 
87
96
    layer = LaunchpadFunctionalLayer
110
119
        self.getUserBrowser(url, person_no_teams)
111
120
        # This may seem large: it is; there is easily another 30% fat in
112
121
        # there.
113
 
        self.assertThat(recorder, HasQueryCount(LessThan(76)))
 
122
        self.assertThat(recorder, HasQueryCount(LessThan(84)))
114
123
        count_with_no_teams = recorder.count
115
124
        # count with many teams
116
125
        self.invalidate_caches(task)
126
135
    def test_rendered_query_counts_constant_with_attachments(self):
127
136
        with celebrity_logged_in('admin'):
128
137
            browses_under_limit = BrowsesWithQueryLimit(
129
 
                82, self.factory.makePerson())
 
138
                86, self.factory.makePerson())
130
139
 
131
140
            # First test with a single attachment.
132
141
            task = self.factory.makeBugTask()
561
570
 
562
571
        request = LaunchpadTestRequest()
563
572
        foo_bugtasks_and_nominations_view = getMultiAdapter(
564
 
            (foo_bug, request), name="+bugtasks-and-nominations-table")
 
573
            (foo_bug, request), name="+bugtasks-and-nominations-portal")
565
574
        foo_bugtasks_and_nominations_view.initialize()
566
575
 
567
576
        task_and_nomination_views = (
585
594
 
586
595
        request = LaunchpadTestRequest()
587
596
        foo_bugtasks_and_nominations_view = getMultiAdapter(
588
 
            (foo_bug, request), name="+bugtasks-and-nominations-table")
 
597
            (foo_bug, request), name="+bugtasks-and-nominations-portal")
589
598
        foo_bugtasks_and_nominations_view.initialize()
590
599
 
591
600
        task_and_nomination_views = (
617
626
        self.assertIn(series.product.displayname, content)
618
627
 
619
628
 
 
629
class TestBugTaskDeleteLinks(TestCaseWithFactory):
 
630
    """ Test that the delete icons/links are correctly rendered.
 
631
 
 
632
        Bug task deletion is protected by a feature flag.
 
633
        """
 
634
 
 
635
    layer = DatabaseFunctionalLayer
 
636
 
 
637
    def test_cannot_delete_only_bugtask(self):
 
638
        # The last bugtask cannot be deleted.
 
639
        bug = self.factory.makeBug()
 
640
        login_person(bug.owner)
 
641
        view = create_initialized_view(
 
642
            bug, name='+bugtasks-and-nominations-table')
 
643
        row_view = view._getTableRowView(bug.default_bugtask, False, False)
 
644
        self.assertFalse(row_view.user_can_delete_bugtask)
 
645
        del get_property_cache(row_view).user_can_delete_bugtask
 
646
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
647
            self.assertFalse(row_view.user_can_delete_bugtask)
 
648
 
 
649
    def test_can_delete_bugtask_if_authorised(self):
 
650
        # The bugtask can be deleted if the user if authorised.
 
651
        bug = self.factory.makeBug()
 
652
        bugtask = self.factory.makeBugTask(bug=bug)
 
653
        login_person(bugtask.owner)
 
654
        view = create_initialized_view(
 
655
            bug, name='+bugtasks-and-nominations-table',
 
656
            principal=bugtask.owner)
 
657
        row_view = view._getTableRowView(bugtask, False, False)
 
658
        self.assertFalse(row_view.user_can_delete_bugtask)
 
659
        del get_property_cache(row_view).user_can_delete_bugtask
 
660
        clear_cache()
 
661
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
662
            self.assertTrue(row_view.user_can_delete_bugtask)
 
663
 
 
664
    def test_bugtask_delete_icon(self):
 
665
        # The bugtask delete icon is rendered correctly for those tasks the
 
666
        # user is allowed to delete.
 
667
        bug = self.factory.makeBug()
 
668
        bugtask_owner = self.factory.makePerson()
 
669
        bugtask = self.factory.makeBugTask(bug=bug, owner=bugtask_owner)
 
670
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
671
            login_person(bugtask.owner)
 
672
            getUtility(ILaunchBag).add(bug.default_bugtask)
 
673
            view = create_initialized_view(
 
674
                bug, name='+bugtasks-and-nominations-table',
 
675
                principal=bugtask.owner)
 
676
            # We render the bug task table rows - there are 2 bug tasks.
 
677
            subviews = view.getBugTaskAndNominationViews()
 
678
            self.assertEqual(2, len(subviews))
 
679
            default_bugtask_contents = subviews[0]()
 
680
            bugtask_contents = subviews[1]()
 
681
            # bugtask can be deleted because the user owns it.
 
682
            delete_icon = find_tag_by_id(
 
683
                bugtask_contents, 'bugtask-delete-task%d' % bugtask.id)
 
684
            delete_url = canonical_url(
 
685
                bugtask, rootsite='bugs', view_name='+delete')
 
686
            self.assertEqual(delete_url, delete_icon['href'])
 
687
            # default_bugtask cannot be deleted.
 
688
            delete_icon = find_tag_by_id(
 
689
                default_bugtask_contents,
 
690
                'bugtask-delete-task%d' % bug.default_bugtask.id)
 
691
            self.assertIsNone(delete_icon)
 
692
 
 
693
    def test_client_cache_contents(self):
 
694
        """ Test that the client cache contains the expected data.
 
695
 
 
696
        The cache data is used by the Javascript to enable the delete
 
697
        links to work as expected.
 
698
        """
 
699
        bug = self.factory.makeBug()
 
700
        bugtask_owner = self.factory.makePerson()
 
701
        bugtask = self.factory.makeBugTask(bug=bug, owner=bugtask_owner)
 
702
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
703
            login_person(bugtask.owner)
 
704
            getUtility(ILaunchBag).add(bug.default_bugtask)
 
705
            view = create_initialized_view(
 
706
                bug, name='+bugtasks-and-nominations-table',
 
707
                principal=bugtask.owner)
 
708
            view.render()
 
709
            cache = IJSONRequestCache(view.request)
 
710
            all_bugtask_data = cache.objects['bugtask_data']
 
711
 
 
712
            def check_bugtask_data(bugtask, can_delete):
 
713
                self.assertIn(bugtask.id, all_bugtask_data)
 
714
                bugtask_data = all_bugtask_data[bugtask.id]
 
715
                self.assertEqual(
 
716
                    'task%d' % bugtask.id, bugtask_data['form_row_id'])
 
717
                self.assertEqual(
 
718
                    'tasksummary%d' % bugtask.id, bugtask_data['row_id'])
 
719
                self.assertEqual(can_delete, bugtask_data['user_can_delete'])
 
720
 
 
721
            check_bugtask_data(bug.default_bugtask, False)
 
722
            check_bugtask_data(bugtask, True)
 
723
 
 
724
 
 
725
class TestBugTaskDeleteView(TestCaseWithFactory):
 
726
    """Test the bug task delete form."""
 
727
 
 
728
    layer = DatabaseFunctionalLayer
 
729
 
 
730
    def test_delete_view_rendering(self):
 
731
        # Test the view rendering, including confirmation message, cancel url.
 
732
        bug = self.factory.makeBug()
 
733
        bugtask = self.factory.makeBugTask(bug=bug)
 
734
        bug_url = canonical_url(bugtask.bug, rootsite='bugs')
 
735
        # Set up request so that the ReturnToReferrerMixin can correctly
 
736
        # extra the referer url.
 
737
        server_url = canonical_url(
 
738
            getUtility(ILaunchpadRoot), rootsite='bugs')
 
739
        extra = {'HTTP_REFERER': bug_url}
 
740
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
741
            login_person(bugtask.owner)
 
742
            view = create_initialized_view(
 
743
                bugtask, name='+delete', principal=bugtask.owner,
 
744
                server_url=server_url, **extra)
 
745
            contents = view.render()
 
746
            confirmation_message = find_tag_by_id(
 
747
                contents, 'confirmation-message')
 
748
            self.assertIsNotNone(confirmation_message)
 
749
            self.assertEqual(bug_url, view.cancel_url)
 
750
 
 
751
    def test_delete_action(self):
 
752
        # Test that the delete action works as expected.
 
753
        bug = self.factory.makeBug()
 
754
        bugtask = self.factory.makeBugTask(bug=bug)
 
755
        target_name = bugtask.bugtargetdisplayname
 
756
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
757
            login_person(bugtask.owner)
 
758
            form = {
 
759
                'field.actions.delete_bugtask': 'Delete',
 
760
                }
 
761
            view = create_initialized_view(
 
762
                bugtask, name='+delete', form=form, principal=bugtask.owner)
 
763
            self.assertEqual([bug.default_bugtask], bug.bugtasks)
 
764
            notifications = view.request.response.notifications
 
765
            self.assertEqual(1, len(notifications))
 
766
            expected = 'This bug no longer affects %s.' % target_name
 
767
            self.assertEqual(expected, notifications[0].message)
 
768
 
 
769
    def _create_bugtask_to_delete(self):
 
770
        bug = self.factory.makeBug()
 
771
        bugtask = self.factory.makeBugTask(bug=bug)
 
772
        target_name = bugtask.bugtargetdisplayname
 
773
        bugtask_url = canonical_url(bugtask, rootsite='bugs')
 
774
        return bug, bugtask, target_name, bugtask_url
 
775
 
 
776
    def test_ajax_delete_current_bugtask(self):
 
777
        # Test that deleting the current bugtask returns a JSON dict
 
778
        # containing the URL of the bug's default task to redirect to.
 
779
        bug, bugtask, target_name, bugtask_url = (
 
780
            self._create_bugtask_to_delete())
 
781
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
782
            login_person(bugtask.owner)
 
783
            # Set up the request so that we correctly simulate an XHR call
 
784
            # from the URL of the bugtask we are deleting.
 
785
            server_url = canonical_url(
 
786
                getUtility(ILaunchpadRoot), rootsite='bugs')
 
787
            extra = {
 
788
                'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
 
789
                'HTTP_REFERER': bugtask_url,
 
790
                }
 
791
            form = {
 
792
                'field.actions.delete_bugtask': 'Delete'
 
793
                }
 
794
            view = create_initialized_view(
 
795
                bugtask, name='+delete', server_url=server_url, form=form,
 
796
                principal=bugtask.owner, **extra)
 
797
            result_data = simplejson.loads(view.render())
 
798
            self.assertEqual([bug.default_bugtask], bug.bugtasks)
 
799
            notifications = simplejson.loads(
 
800
                view.request.response.getHeader('X-Lazr-Notifications'))
 
801
            self.assertEqual(1, len(notifications))
 
802
            expected = 'This bug no longer affects %s.' % target_name
 
803
            self.assertEqual(expected, notifications[0][1])
 
804
            self.assertEqual(
 
805
                'application/json',
 
806
                view.request.response.getHeader('content-type'))
 
807
            expected_url = canonical_url(bug.default_bugtask, rootsite='bugs')
 
808
            self.assertEqual(dict(bugtask_url=expected_url), result_data)
 
809
 
 
810
    def test_ajax_delete_non_current_bugtask(self):
 
811
        # Test that deleting the non-current bugtask returns the new bugtasks
 
812
        # table as HTML.
 
813
        bug, bugtask, target_name, bugtask_url = (
 
814
            self._create_bugtask_to_delete())
 
815
        default_bugtask_url = canonical_url(
 
816
            bug.default_bugtask, rootsite='bugs')
 
817
        with FeatureFixture(DELETE_BUGTASK_ENABLED):
 
818
            login_person(bugtask.owner)
 
819
            # Set up the request so that we correctly simulate an XHR call
 
820
            # from the URL of the default bugtask, not the one we are
 
821
            # deleting.
 
822
            server_url = canonical_url(
 
823
                getUtility(ILaunchpadRoot), rootsite='bugs')
 
824
            extra = {
 
825
                'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest',
 
826
                'HTTP_REFERER': default_bugtask_url,
 
827
                }
 
828
            form = {
 
829
                'field.actions.delete_bugtask': 'Delete'
 
830
                }
 
831
            view = create_initialized_view(
 
832
                bugtask, name='+delete', server_url=server_url, form=form,
 
833
                principal=bugtask.owner, **extra)
 
834
            result_html = view.render()
 
835
            self.assertEqual([bug.default_bugtask], bug.bugtasks)
 
836
            notifications = view.request.response.notifications
 
837
            self.assertEqual(1, len(notifications))
 
838
            expected = 'This bug no longer affects %s.' % target_name
 
839
            self.assertEqual(expected, notifications[0].message)
 
840
            self.assertEqual(
 
841
                view.request.response.getHeader('content-type'), 'text/html')
 
842
            table = find_tag_by_id(result_html, 'affected-software')
 
843
            self.assertIsNotNone(table)
 
844
            [row] = table.tbody.findAll('tr', {'class': 'highlight'})
 
845
            target_link = row.find('a', {'class': 'sprite product'})
 
846
            self.assertIn(
 
847
                bug.default_bugtask.bugtargetdisplayname, target_link)
 
848
 
 
849
 
620
850
class TestBugTasksAndNominationsViewAlsoAffects(TestCaseWithFactory):
621
851
    """ Tests the boolean methods on the view used to indicate whether the
622
852
        Also Affects... links should be allowed or not. Currently these
634
864
    def _createView(self, bug):
635
865
        request = LaunchpadTestRequest()
636
866
        bugtasks_and_nominations_view = getMultiAdapter(
637
 
            (bug, request), name="+bugtasks-and-nominations-table")
 
867
            (bug, request), name="+bugtasks-and-nominations-portal")
638
868
        return bugtasks_and_nominations_view
639
869
 
640
870
    def test_project_bug_cannot_affect_something_else(self):