def test_attachments(self):
        # A bug's attachments and the union of its messages' attachments
        # are the same set.
        with admin_logged_in():
            bug = self.factory.makeBug()
            created_attachment_ids = set(
                self.factory.makeBugAttachment(bug).id for i in range(3))
            bug_url = api_url(bug)
        self.assertThat(created_attachment_ids, HasLength(3))
        logout()

        webservice = LaunchpadWebServiceCaller('test', None)
        bug_attachments = webservice.get(
            bug_url + '/attachments').jsonBody()['entries']
        bug_attachment_ids = set(
            int(att['self_link'].rsplit('/', 1)[1]) for att in bug_attachments)
        self.assertContentEqual(created_attachment_ids, bug_attachment_ids)

        messages = webservice.get(bug_url + '/messages').jsonBody()['entries']
        message_attachments = []
        for message in messages[1:]:
            attachments_url = message['bug_attachments_collection_link']
            attachments = webservice.get(attachments_url).jsonBody()['entries']
            self.assertThat(attachments, HasLength(1))
            message_attachments.append(attachments[0])
        message_attachment_ids = set(
            int(att['self_link'].rsplit('/', 1)[1])
            for att in message_attachments)
        self.assertContentEqual(bug_attachment_ids, message_attachment_ids)
 def test_attachments_query_counts_constant(self):
     # XXX j.c.sackett 2010-09-02 bug=619017
     # This test was being thrown off by the reference bug. To get around
     # the problem, flush and reset are called on the bug storm cache
     # before each call to the webservice. When lp's storm is updated
     # to release the committed fix for this bug, please see about
     # updating this test.
     login(USER_EMAIL)
     self.bug = self.factory.makeBug()
     store = Store.of(self.bug)
     self.factory.makeBugAttachment(self.bug)
     self.factory.makeBugAttachment(self.bug)
     webservice = LaunchpadWebServiceCaller('launchpad-library',
                                            'salgado-change-anything')
     collector = RequestTimelineCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     url = '/bugs/%d/attachments?ws.size=75' % self.bug.id
     # First request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(LessThan(24)))
     with_2_count = collector.count
     self.assertEqual(response.status, 200)
     login(USER_EMAIL)
     for i in range(5):
         self.factory.makeBugAttachment(self.bug)
     logout()
     # Second request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(Equals(with_2_count)))
 def test_messages_query_counts_constant(self):
     # XXX Robert Collins 2010-09-15 bug=619017
     # This test may be thrown off by the reference bug. To get around the
     # problem, flush and reset are called on the bug storm cache before
     # each call to the webservice. When lp's storm is updated to release
     # the committed fix for this bug, please see about updating this test.
     login(USER_EMAIL)
     bug = self.factory.makeBug()
     store = Store.of(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     webservice = LaunchpadWebServiceCaller('launchpad-library',
                                            'salgado-change-anything')
     collector = QueryCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     url = '/bugs/%d/messages?ws.size=75' % bug.id
     # First request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(LessThan(24)))
     with_2_count = collector.count
     self.failUnlessEqual(response.status, 200)
     login(USER_EMAIL)
     for i in range(50):
         self.factory.makeBugComment(bug)
     self.factory.makeBugAttachment(bug)
     logout()
     # Second request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(Equals(with_2_count)))
 def test_messages_query_counts_constant(self):
     # XXX Robert Collins 2010-09-15 bug=619017
     # This test may be thrown off by the reference bug. To get around the
     # problem, flush and reset are called on the bug storm cache before
     # each call to the webservice. When lp's storm is updated to release
     # the committed fix for this bug, please see about updating this test.
     login(USER_EMAIL)
     bug = self.factory.makeBug()
     store = Store.of(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     self.factory.makeBugComment(bug)
     webservice = LaunchpadWebServiceCaller(
         'launchpad-library', 'salgado-change-anything')
     collector = QueryCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     url = '/bugs/%d/messages?ws.size=75' % bug.id
     # First request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(LessThan(24)))
     with_2_count = collector.count
     self.failUnlessEqual(response.status, 200)
     login(USER_EMAIL)
     for i in range(50):
         self.factory.makeBugComment(bug)
     self.factory.makeBugAttachment(bug)
     logout()
     # Second request.
     store.flush()
     store.reset()
     response = webservice.get(url)
     self.assertThat(collector, HasQueryCount(Equals(with_2_count)))
class TestBugDescriptionRepresentation(TestCaseWithFactory):
    """Test ways of interacting with Bug webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login(ADMIN_EMAIL)
        # Make two bugs, one whose description points to the other, so it will
        # get turned into a HTML link.
        self.bug_one = self.factory.makeBug(title="generic")
        self.bug_two = self.factory.makeBug(
            description="Useless bugs are useless. See Bug %d." % (
            self.bug_one.id))

        self.webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')

    def findBugDescription(self, response):
        """Find the bug description field in an XHTML document fragment."""
        soup = BeautifulSoup(response.body)
        dt = soup.find('dt', text="description").parent
        dd = dt.findNextSibling('dd')
        return str(dd.contents.pop())

    def test_GET_xhtml_representation(self):
        response = self.webservice.get(
            '/bugs/' + str(self.bug_two.id),
            'application/xhtml+xml')
        self.assertEqual(response.status, 200)

        self.assertEqual(
            self.findBugDescription(response),
            u'<p>Useless bugs are useless. '
            'See <a href="/bugs/%d" class="bug-link">Bug %d</a>.</p>' % (
            self.bug_one.id, self.bug_one.id))

    def test_PATCH_xhtml_representation(self):
        new_description = "See bug %d" % self.bug_one.id

        bug_two_json = self.webservice.get(
            '/bugs/%d' % self.bug_two.id).jsonBody()

        response = self.webservice.patch(
            bug_two_json['self_link'],
            'application/json',
            dumps(dict(description=new_description)),
            headers=dict(accept='application/xhtml+xml'))

        self.assertEqual(response.status, 209)

        self.assertEqual(
            self.findBugDescription(response),
            u'<p>See <a href="/bugs/%d" class="bug-link">bug %d</a></p>' % (
            self.bug_one.id, self.bug_one.id))
class TestBugDescriptionRepresentation(TestCaseWithFactory):
    """Test ways of interacting with Bug webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login(ADMIN_EMAIL)
        # Make two bugs, one whose description points to the other, so it will
        # get turned into a HTML link.
        self.bug_one = self.factory.makeBug(title="generic")
        self.bug_two = self.factory.makeBug(
            description="Useless bugs are useless. See Bug %d." %
            (self.bug_one.id))

        self.webservice = LaunchpadWebServiceCaller('launchpad-library',
                                                    'salgado-change-anything')

    def findBugDescription(self, response):
        """Find the bug description field in an XHTML document fragment."""
        soup = BeautifulSoup(response.body)
        dt = soup.find('dt', text="description").parent
        dd = dt.findNextSibling('dd')
        return str(dd.contents.pop())

    def test_GET_xhtml_representation(self):
        response = self.webservice.get('/bugs/' + str(self.bug_two.id),
                                       'application/xhtml+xml')
        self.assertEqual(response.status, 200)

        self.assertEqual(
            self.findBugDescription(response), u'<p>Useless bugs are useless. '
            'See <a href="/bugs/%d" class="bug-link">Bug %d</a>.</p>' %
            (self.bug_one.id, self.bug_one.id))

    def test_PATCH_xhtml_representation(self):
        new_description = "See bug %d" % self.bug_one.id

        bug_two_json = self.webservice.get('/bugs/%d' %
                                           self.bug_two.id).jsonBody()

        response = self.webservice.patch(
            bug_two_json['self_link'],
            'application/json',
            dumps(dict(description=new_description)),
            headers=dict(accept='application/xhtml+xml'))

        self.assertEqual(response.status, 209)

        self.assertEqual(
            self.findBugDescription(response),
            u'<p>See <a href="/bugs/%d" class="bug-link">bug %d</a></p>' %
            (self.bug_one.id, self.bug_one.id))
class ProcessorSetWebServiceTests(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(ProcessorSetWebServiceTests, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_getByName(self):
        self.factory.makeProcessor(name='transmeta')
        logout()

        processor = self.webservice.named_get(
            '/+processors', 'getByName', name='transmeta',
            api_version='devel').jsonBody()
        self.assertEquals('transmeta', processor['name'])

    def test_default_collection(self):
        # Make it easy to filter out sample data
        store = IStore(Processor)
        store.execute("UPDATE Processor SET name = 'sample_data_' || name")
        self.factory.makeProcessor(name='q1')
        self.factory.makeProcessor(name='i686')
        self.factory.makeProcessor(name='g4')

        logout()

        collection = self.webservice.get(
            '/+processors?ws.size=10', api_version='devel').jsonBody()
        self.assertEquals(
            ['g4', 'i686', 'q1'],
            sorted(
            processor['name'] for processor in collection['entries']
            if not processor['name'].startswith('sample_data_')))
class ProcessorSetWebServiceTests(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(ProcessorSetWebServiceTests, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_getByName(self):
        self.factory.makeProcessor(name='transmeta')
        logout()

        processor = self.webservice.named_get('/+processors',
                                              'getByName',
                                              name='transmeta',
                                              api_version='devel').jsonBody()
        self.assertEqual('transmeta', processor['name'])

    def test_default_collection(self):
        # Make it easy to filter out sample data
        store = IStore(Processor)
        store.execute("UPDATE Processor SET name = 'sample_data_' || name")
        self.factory.makeProcessor(name='q1')
        self.factory.makeProcessor(name='i686')
        self.factory.makeProcessor(name='g4')

        logout()

        collection = self.webservice.get('/+processors?ws.size=10',
                                         api_version='devel').jsonBody()
        self.assertEqual(
            ['g4', 'i686', 'q1'],
            sorted(processor['name'] for processor in collection['entries']
                   if not processor['name'].startswith('sample_data_')))
 def test_object_not_found(self):
     """Missing top-level objects generate 404s but not OOPS."""
     webservice = LaunchpadWebServiceCaller(
         'launchpad-library', 'salgado-change-anything')
     response = webservice.get('/%s/123456789' % self.object_type)
     self.assertEqual(response.status, 404)
     self.assertEqual(response.getheader('x-lazr-oopsid'), None)
 def test_alias_redirects_in_webservice(self):
     # When a redirect occurs for a product, it should remain in the
     # webservice.
     product = self.factory.makeProduct(name='lemur')
     removeSecurityProxy(product).setAliases(['monkey'])
     webservice = LaunchpadWebServiceCaller('launchpad-library',
                                            'salgado-change-anything')
     response = webservice.get('/monkey')
     self.assertEqual('http://api.launchpad.dev/beta/lemur',
                      response.getheader('location'))
 def test_alias_redirects_in_webservice(self):
     # When a redirect occurs for a product, it should remain in the
     # webservice.
     product = self.factory.makeProduct(name='lemur')
     removeSecurityProxy(product).setAliases(['monkey'])
     webservice = LaunchpadWebServiceCaller(
         'launchpad-library', 'salgado-change-anything')
     response = webservice.get('/monkey')
     self.assertEqual(
         'http://api.launchpad.dev/beta/lemur',
         response.getheader('location'))
Exemple #12
0
class TestBuilderEntry(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestBuilderEntry, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_exports_processor(self):
        processor = self.factory.makeProcessor('s1')
        builder = self.factory.makeBuilder(processor=processor)

        logout()
        entry = self.webservice.get(api_url(builder),
                                    api_version='devel').jsonBody()
        self.assertEndsWith(entry['processor_link'], '/+processors/s1')
Exemple #13
0
 def test_api_branches_query_count(self):
     webservice = LaunchpadWebServiceCaller()
     collector = RequestTimelineCollector()
     collector.register()
     self.addCleanup(collector.unregister)
     # Get 'all' of the 50 branches this collection is limited to - rather
     # than the default in-test-suite pagination size of 5.
     url = "/branches?ws.size=50"
     logout()
     response = webservice.get(url,
         headers={'User-Agent': 'AnonNeedsThis'})
     self.assertEqual(response.status, 200,
         "Got %d for url %r with response %r" % (
         response.status, url, response.body))
     self.assertThat(collector, HasQueryCount(LessThan(17)))
class TestBuilderEntry(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestBuilderEntry, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_exports_processor(self):
        processor = self.factory.makeProcessor('s1')
        builder = self.factory.makeBuilder(processor=processor)

        logout()
        entry = self.webservice.get(
            api_url(builder), api_version='devel').jsonBody()
        self.assertEndsWith(entry['processor_link'], '/+processors/s1')
class TestBugCommentRepresentation(TestCaseWithFactory):
    """Test ways of interacting with BugComment webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login('[email protected] ')
        self.bug = self.factory.makeBug()
        commenter = self.factory.makePerson()
        self.bug.newMessage(
            commenter, 'Comment Subject', 'Comment content')
        comments = get_comments_for_bugtask(self.bug.bugtasks[0])
        self.comment = comments[1]
        comment_view = getMultiAdapter(
            (self.comment, LaunchpadTestRequest()), name="+box")
        self.expected_comment_html = str(comment_view())
        self.message_path = '/%s/+bug/%s/comments/1' % (
            self.bug.bugtasks[0].product.name, self.bug.id)
        self.webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')

    def assertRenderedCommentsEqual(self, a_comment, another_comment):
        """Assert that two rendered comments are equal.

        It replaces parts that depend of the current time with fixed
        strings, so that two comments rendered at different times are
        still considered equal.
        """
        when_regexp = re.compile(r'>\d+ .*? ago<')
        a_comment = when_regexp.sub('>WHEN<', a_comment)
        another_comment = when_regexp.sub('>WHEN<', another_comment)
        self.assertEqual(a_comment, another_comment)

    def test_GET_xhtml_representation(self):
        # The XHTML of a BugComment is exactly the same as how it's
        # rendered in the web UI. The existing +box view is re-used to
        # render it.
        response = self.webservice.get(
            self.message_path, 'application/xhtml+xml')

        self.assertEqual(response.status, 200)

        rendered_comment = response.body
        self.assertRenderedCommentsEqual(
            rendered_comment, self.expected_comment_html)
class TestBugCommentRepresentation(TestCaseWithFactory):
    """Test ways of interacting with BugComment webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login('[email protected] ')
        self.bug = self.factory.makeBug()
        commenter = self.factory.makePerson()
        self.bug.newMessage(commenter, 'Comment Subject', 'Comment content')
        comments = get_comments_for_bugtask(self.bug.bugtasks[0])
        self.comment = comments[1]
        comment_view = getMultiAdapter((self.comment, LaunchpadTestRequest()),
                                       name="+box")
        self.expected_comment_html = str(comment_view())
        self.message_path = '/%s/+bug/%s/comments/1' % (
            self.bug.bugtasks[0].product.name, self.bug.id)
        self.webservice = LaunchpadWebServiceCaller('launchpad-library',
                                                    'salgado-change-anything')

    def assertRenderedCommentsEqual(self, a_comment, another_comment):
        """Assert that two rendered comments are equal.

        It replaces parts that depend of the current time with fixed
        strings, so that two comments rendered at different times are
        still considered equal.
        """
        when_regexp = re.compile(r'>\d+ .*? ago<')
        a_comment = when_regexp.sub('>WHEN<', a_comment)
        another_comment = when_regexp.sub('>WHEN<', another_comment)
        self.assertEqual(a_comment, another_comment)

    def test_GET_xhtml_representation(self):
        # The XHTML of a BugComment is exactly the same as how it's
        # rendered in the web UI. The existing +box view is re-used to
        # render it.
        response = self.webservice.get(self.message_path,
                                       'application/xhtml+xml')

        self.assertEqual(response.status, 200)

        rendered_comment = response.body
        self.assertRenderedCommentsEqual(rendered_comment,
                                         self.expected_comment_html)
Exemple #17
0
    def test_user_access_to_private_bug_attachment(self):
        # Users having access to private bugs can also read attachments
        # of these bugs.
        self.bug.setPrivate(True, self.bug_owner)
        other_user = self.factory.makePerson()
        launchpad = launchpadlib_for('test', self.bug_owner, version='devel')
        ws_bug = ws_object(launchpad, self.bug)
        ws_bugattachment = ws_bug.attachments[0]

        # The attachment contains a link to a HostedBytes resource;
        # the response to a GET request of this URL is a redirect to a
        # Librarian URL.  We cannot simply access these Librarian URLs
        # for restricted Librarian files because the host name used in
        # the URLs is different for each file, and our test envireonment
        # does not support wildcard DNS, and because the Launchpadlib
        # browser automatically follows redirects.
        # LaunchpadWebServiceCaller, on the other hand, gives us
        # access to a raw HTTPResonse object.
        webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')
        response = webservice.get(ws_bugattachment.data._wadl_resource._url)
        self.assertEqual(303, response.status)

        # The Librarian URL has, for our test case, the form
        # "https://NNNN.restricted.launchpad.dev:PORT/NNNN/foo.txt?token=..."
        # where NNNN and PORT are integers.
        parsed_url = urlparse(response.getHeader('location'))
        self.assertEqual('https', parsed_url.scheme)
        mo = re.search(
            r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc)
        self.assertIsNot(None, mo, parsed_url.netloc)
        mo = re.search(r'^/\d+/foo\.txt$', parsed_url.path)
        self.assertIsNot(None, mo)
        params = parse_qs(parsed_url.query)
        self.assertEqual(['token'], params.keys())

        # If a user which cannot access the private bug itself tries to
        # to access the attachment, an NotFound error is raised.
        other_launchpad = launchpadlib_for(
            'test_unauthenticated', other_user, version='devel')
        self.assertRaises(
            RestfulNotFound, other_launchpad._browser.get,
            ws_bugattachment.data._wadl_resource._url)
    def test_user_access_to_private_bug_attachment(self):
        # Users having access to private bugs can also read attachments
        # of these bugs.
        self.bug.setPrivate(True, self.bug_owner)
        other_user = self.factory.makePerson()
        launchpad = launchpadlib_for('test', self.bug_owner, version='devel')
        ws_bug = ws_object(launchpad, self.bug)
        ws_bugattachment = ws_bug.attachments[0]

        # The attachment contains a link to a HostedBytes resource;
        # the response to a GET request of this URL is a redirect to a
        # Librarian URL.  We cannot simply access these Librarian URLs
        # for restricted Librarian files because the host name used in
        # the URLs is different for each file, and our test envireonment
        # does not support wildcard DNS, and because the Launchpadlib
        # browser automatically follows redirects.
        # LaunchpadWebServiceCaller, on the other hand, gives us
        # access to a raw HTTPResonse object.
        webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')
        response = webservice.get(ws_bugattachment.data._wadl_resource._url)
        self.assertEqual(303, response.status)

        # The Librarian URL has, for our test case, the form
        # "https://NNNN.restricted.launchpad.dev:PORT/NNNN/foo.txt?token=..."
        # where NNNN and PORT are integers.
        parsed_url = urlparse(response.getHeader('location'))
        self.assertEqual('https', parsed_url.scheme)
        mo = re.search(
            r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc)
        self.assertIsNot(None, mo, parsed_url.netloc)
        mo = re.search(r'^/\d+/foo\.txt$', parsed_url.path)
        self.assertIsNot(None, mo)
        params = parse_qs(parsed_url.query)
        self.assertEqual(['token'], params.keys())

        # If a user which cannot access the private bug itself tries to
        # to access the attachment, an NotFound error is raised.
        other_launchpad = launchpadlib_for(
            'test_unauthenticated', other_user, version='devel')
        self.assertRaises(
            RestfulNotFound, other_launchpad._browser.get,
            ws_bugattachment.data._wadl_resource._url)
class TestPersonRepresentation(TestCaseWithFactory):

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login('[email protected] ')
        self.person = self.factory.makePerson(
            name='test-person', displayname='Test Person')
        self.webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')

    def test_GET_xhtml_representation(self):
        # Remove the security proxy because IPerson.name is protected.
        person_name = removeSecurityProxy(self.person).name
        response = self.webservice.get(
            '/~%s' % person_name, 'application/xhtml+xml')

        self.assertEqual(response.status, 200)

        rendered_comment = response.body
        self.assertEquals(
            rendered_comment,
            '<a href="/~test-person" class="sprite person">Test Person</a>')
Exemple #20
0
class TestPersonRepresentation(TestCaseWithFactory):

    layer = DatabaseFunctionalLayer

    def setUp(self):
        TestCaseWithFactory.setUp(self)
        login('[email protected] ')
        self.person = self.factory.makePerson(name='test-person',
                                              displayname='Test Person')
        self.webservice = LaunchpadWebServiceCaller('launchpad-library',
                                                    'salgado-change-anything')

    def test_GET_xhtml_representation(self):
        # Remove the security proxy because IPerson.name is protected.
        person_name = removeSecurityProxy(self.person).name
        response = self.webservice.get('/~%s' % person_name,
                                       'application/xhtml+xml')

        self.assertEqual(response.status, 200)

        rendered_comment = response.body
        self.assertEquals(
            rendered_comment,
            '<a href="/~test-person" class="sprite person">Test Person</a>')
class PersonSetWebServiceTests(TestCaseWithFactory):

    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(PersonSetWebServiceTests, self).setUp()
        self.webservice = LaunchpadWebServiceCaller('test', None)
        logout()

    def assertReturnsPeople(self, expected_names, path):
        self.assertEqual(
            expected_names,
            [person['name'] for person in
             self.webservice.get(path).jsonBody()['entries']])

    def test_default_content(self):
        # /people lists the 50 people with the most karma, excluding
        # those with no karma at all.
        self.assertEqual(
            4, len(self.webservice.get('/people').jsonBody()['entries']))

    def test_find(self):
        # It's possible to find people by name.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
        self.assertReturnsPeople(
            [person_name], '/people?ws.op=find&text=%s' % person_name)

    def test_findTeam(self):
        # The search can be restricted to teams.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            team_name = self.factory.makeTeam(
                name='%s-team' % person_name).name
        self.assertReturnsPeople(
            [team_name], '/people?ws.op=findTeam&text=%s' % person_name)

    def test_findTeam_query_count(self):
        with admin_logged_in():
            ws = webservice_for_person(self.factory.makePerson())

        def create_match():
            with admin_logged_in():
                self.factory.makeTeam(displayname='foobar')

        def find_teams():
            ws.named_get('/people', 'findTeam', text="foobar").jsonBody()

        # Ensure that we're already in a stable cache state.
        find_teams()
        recorder1, recorder2 = record_two_runs(
            find_teams, create_match, 2)
        self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))

    def test_findPerson(self):
        # The search can be restricted to people.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            self.factory.makeTeam(name='%s-team' % person_name)
        self.assertReturnsPeople(
            [person_name], '/people?ws.op=findPerson&text=%s' % person_name)

    def test_find_by_date(self):
        # Creation date filtering is supported.
        self.assertReturnsPeople(
            [u'bac'],
            '/people?ws.op=findPerson&text='
            '&created_after=2008-06-27&created_before=2008-07-01')

    def test_getByEmail(self):
        # You can get a person by their email address.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_email = person.preferredemail.email
        self.assertEqual(
            person_name,
            self.webservice.get(
                '/people?ws.op=getByEmail&email=%s' % person_email
                ).jsonBody()['name'])

    def test_getByEmail_checks_format(self):
        # A malformed email address is rejected.
        e = self.assertRaises(
            ValueError,
            self.webservice.get(
                '/people?ws.op=getByEmail&email=foo@').jsonBody)
        # XXX wgrant bug=1088358: This escaping shouldn't be here; it's
        # not HTML.
        self.assertEqual("email: Invalid email &#x27;foo@&#x27;.", e[0])

    def test_getByOpenIDIdentifier(self):
        # You can get a person by their OpenID identifier URL.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_openid = person.account.openid_identifiers.one().identifier
        self.assertEqual(
            person_name,
            self.webservice.get(
                '/people?ws.op=getByOpenIDIdentifier&'
                'identifier=http://login1.dev/%%2Bid/%s'
                % person_openid,
                api_version='devel').jsonBody()['name'])

    def getOrCreateSoftwareCenterCustomer(self, user):
        webservice = webservice_for_person(
            user, permission=OAuthPermission.WRITE_PRIVATE)
        response = webservice.named_post(
            '/people', 'getOrCreateSoftwareCenterCustomer',
            openid_identifier='somebody',
            email_address='*****@*****.**', display_name='Somebody',
            api_version='devel')
        return response

    def test_getOrCreateSoftwareCenterCustomer(self):
        # Software Center Agent (SCA) can get or create people by OpenID
        # identifier.
        with admin_logged_in():
            sca = getUtility(IPersonSet).getByName('software-center-agent')
        response = self.getOrCreateSoftwareCenterCustomer(sca)
        self.assertEqual('Somebody', response.jsonBody()['display_name'])
        with admin_logged_in():
            person = getUtility(IPersonSet).getByEmail('*****@*****.**')
            self.assertEqual('Somebody', person.displayname)
            self.assertEqual(
                ['somebody'],
                [oid.identifier for oid in person.account.openid_identifiers])
            self.assertEqual(
                '*****@*****.**', person.preferredemail.email)

    def test_getOrCreateSoftwareCenterCustomer_is_restricted(self):
        # The method may only be invoked by the ~software-center-agent
        # celebrity user, as it is security-sensitive.
        with admin_logged_in():
            random = self.factory.makePerson()
        response = self.getOrCreateSoftwareCenterCustomer(random)
        self.assertEqual(401, response.status)

    def test_getOrCreateSoftwareCenterCustomer_rejects_email_conflicts(self):
        # An unknown OpenID identifier with a known email address causes
        # the request to fail with 409 Conflict, as we'd otherwise end
        # up linking the OpenID identifier to an existing account.
        with admin_logged_in():
            self.factory.makePerson(email='*****@*****.**')
            sca = getUtility(IPersonSet).getByName('software-center-agent')
        response = self.getOrCreateSoftwareCenterCustomer(sca)
        self.assertEqual(409, response.status)

    def test_getOrCreateSoftwareCenterCustomer_rejects_suspended(self):
        # Suspended accounts are not returned.
        with admin_logged_in():
            existing = self.factory.makePerson(
                email='*****@*****.**',
                account_status=AccountStatus.SUSPENDED)
            oid = OpenIdIdentifier()
            oid.account = existing.account
            oid.identifier = u'somebody'
            Store.of(existing).add(oid)
            sca = getUtility(IPersonSet).getByName('software-center-agent')
        response = self.getOrCreateSoftwareCenterCustomer(sca)
        self.assertEqual(400, response.status)

    def test_getUsernameForSSO(self):
        # canonical-identity-provider (SSO) can get the username for an
        # OpenID identifier suffix.
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
            existing = self.factory.makePerson(name='username')
            taken_openid = (
                existing.account.openid_identifiers.any().identifier)
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.READ_PUBLIC)
        response = webservice.named_get(
            '/people', 'getUsernameForSSO',
            openid_identifier=taken_openid, api_version='devel')
        self.assertEqual(200, response.status)
        self.assertEqual('username', response.jsonBody())

    def test_getUsernameForSSO_nonexistent(self):
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.READ_PUBLIC)
        response = webservice.named_get(
            '/people', 'getUsernameForSSO',
            openid_identifier='doesnotexist', api_version='devel')
        self.assertEqual(200, response.status)
        self.assertEqual(None, response.jsonBody())

    def setUsernameFromSSO(self, user, openid_identifier, name,
                           dry_run=False):
        webservice = webservice_for_person(
            user, permission=OAuthPermission.WRITE_PRIVATE)
        response = webservice.named_post(
            '/people', 'setUsernameFromSSO',
            openid_identifier=openid_identifier, name=name, dry_run=dry_run,
            api_version='devel')
        return response

    def test_setUsernameFromSSO(self):
        # canonical-identity-provider (SSO) can create a placeholder
        # Person to give a username to a non-LP user.
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        response = self.setUsernameFromSSO(sso, 'foo', 'bar')
        self.assertEqual(200, response.status)
        with admin_logged_in():
            by_name = getUtility(IPersonSet).getByName('bar')
            by_openid = getUtility(IPersonSet).getByOpenIDIdentifier(
                u'http://testopenid.dev/+id/foo')
            self.assertEqual(by_name, by_openid)
            self.assertEqual(
                AccountStatus.PLACEHOLDER, by_name.account_status)

    def test_setUsernameFromSSO_dry_run(self):
        # setUsernameFromSSO provides a dry run mode that performs all
        # the checks but doesn't actually make changes. Useful for input
        # validation in SSO.
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        response = self.setUsernameFromSSO(sso, 'foo', 'bar', dry_run=True)
        self.assertEqual(200, response.status)
        with admin_logged_in():
            self.assertIs(None, getUtility(IPersonSet).getByName('bar'))
            self.assertRaises(
                LookupError,
                getUtility(IAccountSet).getByOpenIDIdentifier, u'foo')

    def test_setUsernameFromSSO_is_restricted(self):
        # The method may only be invoked by the ~ubuntu-sso celebrity
        # user, as it is security-sensitive.
        with admin_logged_in():
            random = self.factory.makePerson()
        response = self.setUsernameFromSSO(random, 'foo', 'bar')
        self.assertEqual(401, response.status)

    def test_setUsernameFromSSO_rejects_bad_input(self, dry_run=False):
        # The method returns meaningful errors on bad input, so SSO can
        # give advice to users.
        # Check canonical-identity-provider before changing these!
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
            self.factory.makePerson(name='taken-name')
            existing = self.factory.makePerson()
            taken_openid = (
                existing.account.openid_identifiers.any().identifier)

        response = self.setUsernameFromSSO(
            sso, 'foo', 'taken-name', dry_run=dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            'name: taken-name is already in use by another person or team.',
            response.body)

        response = self.setUsernameFromSSO(
            sso, 'foo', 'private-name', dry_run=dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            'name: The name &#x27;private-name&#x27; has been blocked by the '
            'Launchpad administrators. Contact Launchpad Support if you want '
            'to use this name.',
            response.body)

        response = self.setUsernameFromSSO(
            sso, taken_openid, 'bar', dry_run=dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            'An account for that OpenID identifier already exists.',
            response.body)

    def test_setUsernameFromSSO_rejects_bad_input_in_dry_run(self):
        self.test_setUsernameFromSSO_rejects_bad_input(dry_run=True)

    def test_getSSHKeysForSSO(self):
        with admin_logged_in():
            target = self.factory.makePerson()
            key = self.factory.makeSSHKey(target)
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
            taken_openid = (
                target.account.openid_identifiers.any().identifier)
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.READ_PUBLIC)
        response = webservice.named_get(
            '/people', 'getSSHKeysForSSO',
            openid_identifier=taken_openid, api_version='devel')
        expected = 'ssh-rsa %s %s' % (key.keytext, key.comment)
        self.assertEqual(200, response.status)
        self.assertEqual([expected], response.jsonBody())

    def test_getSSHKeysForSSO_nonexistent(self):
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.READ_PUBLIC)
        response = webservice.named_get(
            '/people', 'getSSHKeysForSSO',
            openid_identifier='doesnotexist', api_version='devel')
        self.assertEqual(200, response.status)
        self.assertEqual(None, response.jsonBody())

    def addSSHKeyForPerson(self, openid_identifier, key_text, dry_run=False):
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.WRITE_PRIVATE)
        return webservice.named_post(
            '/people', 'addSSHKeyFromSSO',
            openid_identifier=openid_identifier, key_text=key_text,
            dry_run=dry_run, api_version='devel')

    def test_addSSHKeyFromSSO_nonexistant(self):
        response = self.addSSHKeyForPerson('doesnotexist', 'sdf')
        self.assertEqual(400, response.status)
        self.assertEqual(
            "No account found for openid identifier 'doesnotexist'",
            response.body)

    def test_addSSHKeyFromSSO_rejects_bad_key_data(self):
        with admin_logged_in():
            person = self.factory.makePerson()
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.addSSHKeyForPerson(openid_id, 'bad_data')
        self.assertEqual(400, response.status)
        self.assertEqual(
            "Invalid SSH key data: 'bad_data'",
            response.body)

    def test_addSSHKeyFromSSO_rejects_bad_key_type(self):
        with admin_logged_in():
            person = self.factory.makePerson()
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.addSSHKeyForPerson(openid_id, 'foo keydata comment')
        self.assertEqual(400, response.status)
        self.assertEqual(
            "Invalid SSH key type: 'foo'",
            response.body)

    def test_addSSHKeyFromSSO_rejects_bad_key_type_dry_run(self):
        with admin_logged_in():
            person = self.factory.makePerson()
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.addSSHKeyForPerson(
            openid_id, 'foo keydata comment', True)
        self.assertEqual(400, response.status)
        self.assertEqual(
            "Invalid SSH key type: 'foo'",
            response.body)

    def test_addSSHKeyFromSSO_works(self):
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            openid_id = person.account.openid_identifiers.any().identifier
        full_key = self.factory.makeSSHKeyText()
        _, keytext, comment = full_key.split(' ', 2)
        response = self.addSSHKeyForPerson(openid_id, full_key)

        self.assertEqual(200, response.status)
        [key] = person.sshkeys
        self.assertEqual(SSHKeyType.RSA, key.keytype)
        self.assertEqual(keytext, key.keytext)
        self.assertEqual(comment, key.comment)

    def test_addSSHKeyFromSSO_dry_run(self):
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.addSSHKeyForPerson(
            openid_id, self.factory.makeSSHKeyText(), dry_run=True)

        self.assertEqual(200, response.status)
        self.assertEqual(0, person.sshkeys.count())

    def test_addSSHKeyFromSSO_is_restricted(self):
        with admin_logged_in():
            target = self.factory.makePerson()
            openid_id = target.account.openid_identifiers.any().identifier
        webservice = webservice_for_person(
            target, permission=OAuthPermission.WRITE_PRIVATE)
        response = webservice.named_post(
            '/people', 'addSSHKeyFromSSO',
            openid_identifier=openid_id, key_text='ssh-rsa foo bar',
            dry_run=False, api_version='devel')
        self.assertEqual(401, response.status)

    def deleteSSHKeyFromSSO(self, openid_identifier, key_text,
                                     dry_run=False):
        with admin_logged_in():
            sso = getUtility(IPersonSet).getByName('ubuntu-sso')
        webservice = webservice_for_person(
            sso, permission=OAuthPermission.WRITE_PRIVATE)
        return webservice.named_post(
            '/people', 'deleteSSHKeyFromSSO',
            openid_identifier=openid_identifier, key_text=key_text,
            dry_run=dry_run, api_version='devel')

    def test_deleteSSHKeyFromSSO_nonexistant(self, dry_run=False):
        response = self.deleteSSHKeyFromSSO(
            'doesnotexist', 'sdf', dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            "No account found for openid identifier 'doesnotexist'",
            response.body)

    def test_deleteSSHKeyFromSSO_nonexistant_dry_run(self):
        self.test_deleteSSHKeyFromSSO_nonexistant(True)

    def test_deleteSSHKeyFromSSO_rejects_bad_key_data(self,
                                                               dry_run=False):
        with admin_logged_in():
            person = self.factory.makePerson()
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, 'bad_data', dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            "Invalid SSH key data: 'bad_data'",
            response.body)

    def test_deleteSSHKeyFromSSO_rejects_bad_key_data_dry_run(self):
        self.test_deleteSSHKeyFromSSO_rejects_bad_key_data(True)

    def test_deleteSSHKeyFromSSO_rejects_bad_key_type(self,
                                                               dry_run=False):
        with admin_logged_in():
            person = self.factory.makePerson()
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, 'foo keydata comment', dry_run)
        self.assertEqual(400, response.status)
        self.assertEqual(
            "Invalid SSH key type: 'foo'",
            response.body)

    def test_deleteSSHKeyFromSSO_rejects_bad_key_type_dry_run(self):
        self.test_deleteSSHKeyFromSSO_rejects_bad_key_type(True)

    def test_deleteSSHKeyFromSSO_works(self):
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            key = self.factory.makeSSHKey(person)
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, key.getFullKeyText())

        self.assertEqual(200, response.status)
        self.assertEqual(0, person.sshkeys.count())

    def test_deleteSSHKeyFromSSO_works_dry_run(self):
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            key = self.factory.makeSSHKey(person)
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, key.getFullKeyText(), dry_run=True)

        self.assertEqual(200, response.status)
        self.assertEqual(1, person.sshkeys.count())

    def test_deleteSSHKeyFromSSO_allows_newlines(self):
        # Adding these should normally be forbidden, but we want users to be
        # able to delete existing rows.
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            kind, data, comment = self.factory.makeSSHKeyText().split(" ", 2)
            key_text = "%s %s %s\n" % (kind, textwrap.fill(data), comment)
            key = getUtility(ISSHKeySet).new(person, key_text, check_key=False)
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, key.getFullKeyText())

        self.assertEqual(200, response.status)
        self.assertEqual(0, person.sshkeys.count())

    def test_deleteSSHKeyFromSSO_allows_newlines_dry_run(self):
        with admin_logged_in():
            person = removeSecurityProxy(self.factory.makePerson())
            kind, data, comment = self.factory.makeSSHKeyText().split(" ", 2)
            key_text = "%s %s %s\n" % (kind, textwrap.fill(data), comment)
            key = getUtility(ISSHKeySet).new(person, key_text, check_key=False)
            openid_id = person.account.openid_identifiers.any().identifier
        response = self.deleteSSHKeyFromSSO(
            openid_id, key.getFullKeyText(), dry_run=True)

        self.assertEqual(200, response.status)
        self.assertEqual(1, person.sshkeys.count())

    def test_deleteSSHKeyFromSSO_is_restricted(self, dry_run=False):
        with admin_logged_in():
            target = self.factory.makePerson()
            openid_id = target.account.openid_identifiers.any().identifier
        webservice = webservice_for_person(
            target, permission=OAuthPermission.WRITE_PRIVATE)
        response = webservice.named_post(
            '/people', 'deleteSSHKeyFromSSO',
            openid_identifier=openid_id, key_text='ssh-rsa foo bar',
            dry_run=dry_run, api_version='devel')
        self.assertEqual(401, response.status)

    def test_deleteSSHKeyFromSSO_is_restricted_dry_run(self):
        self.test_deleteSSHKeyFromSSO_is_restricted(True)
class PersonSetWebServiceTests(TestCaseWithFactory):

    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(PersonSetWebServiceTests, self).setUp()
        self.webservice = LaunchpadWebServiceCaller('test', None)
        logout()

    def assertReturnsPeople(self, expected_names, path):
        self.assertEqual(
            expected_names,
            [person['name'] for person in
             self.webservice.get(path).jsonBody()['entries']])

    def test_default_content(self):
        # /people lists the 50 people with the most karma, excluding
        # those with no karma at all.
        self.assertEqual(
            4, len(self.webservice.get('/people').jsonBody()['entries']))

    def test_find(self):
        # It's possible to find people by name.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
        self.assertReturnsPeople(
            [person_name], '/people?ws.op=find&text=%s' % person_name)

    def test_findTeam(self):
        # The search can be restricted to teams.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            team_name = self.factory.makeTeam(
                name='%s-team' % person_name).name
        self.assertReturnsPeople(
            [team_name], '/people?ws.op=findTeam&text=%s' % person_name)

    def test_findPerson(self):
        # The search can be restricted to people.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            self.factory.makeTeam(name='%s-team' % person_name)
        self.assertReturnsPeople(
            [person_name], '/people?ws.op=findPerson&text=%s' % person_name)

    def test_find_by_date(self):
        # Creation date filtering is supported.
        self.assertReturnsPeople(
            [u'bac'],
            '/people?ws.op=findPerson&text='
            '&created_after=2008-06-27&created_before=2008-07-01')

    def test_getByEmail(self):
        # You can get a person by their email address.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_email = person.preferredemail.email
        self.assertEqual(
            person_name,
            self.webservice.get(
                '/people?ws.op=getByEmail&email=%s' % person_email
                ).jsonBody()['name'])

    def test_getByEmail_checks_format(self):
        # A malformed email address is rejected.
        e = self.assertRaises(
            ValueError,
            self.webservice.get(
                '/people?ws.op=getByEmail&email=foo@').jsonBody)
        # XXX wgrant bug=1088358: This escaping shouldn't be here; it's
        # not HTML.
        self.assertEqual("email: Invalid email &#x27;foo@&#x27;.", e[0])

    def test_getByOpenIDIdentifier(self):
        # You can get a person by their OpenID identifier URL.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_openid = person.account.openid_identifiers.one().identifier
        self.assertEqual(
            person_name,
            self.webservice.get(
                '/people?ws.op=getByOpenIDIdentifier&'
                'identifier=http://openid.launchpad.dev/%%2Bid/%s'
                % person_openid,
                api_version='devel').jsonBody()['name'])
Exemple #23
0
class TestQuestionRepresentation(TestCaseWithFactory):
    """Test ways of interacting with Question webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestQuestionRepresentation, self).setUp()
        with celebrity_logged_in('admin'):
            self.question = self.factory.makeQuestion(
                title="This is a question")
            self.target_name = self.question.target.name

        self.webservice = LaunchpadWebServiceCaller(
            'launchpad-library', 'salgado-change-anything')
        self.webservice.default_api_version = 'devel'

    def findQuestionTitle(self, response):
        """Find the question title field in an XHTML document fragment."""
        soup = BeautifulSoup(response.body)
        dt = soup.find('dt', text="title").parent
        dd = dt.findNextSibling('dd')
        return str(dd.contents.pop())

    def test_top_level_question_get(self):
        # The top level question set can be used via the api to get
        # a question by id via redirect without url hacking.
        response = self.webservice.get(
            '/questions/%s' % self.question.id, 'application/xhtml+xml')
        self.assertEqual(response.status, 200)

    def test_GET_xhtml_representation(self):
        # A question's xhtml representation is available on the api.
        response = self.webservice.get(
            '/%s/+question/%d' % (self.target_name,
                self.question.id),
            'application/xhtml+xml')
        self.assertEqual(response.status, 200)

        self.assertEqual(
            self.findQuestionTitle(response),
            "<p>This is a question</p>")

    def test_PATCH_xhtml_representation(self):
        # You can update the question through the api with PATCH.
        new_title = "No, this is a question"

        question_json = self.webservice.get(
            '/%s/+question/%d' % (self.target_name,
                self.question.id)).jsonBody()

        response = self.webservice.patch(
            question_json['self_link'],
            'application/json',
            dumps(dict(title=new_title)),
            headers=dict(accept='application/xhtml+xml'))

        self.assertEqual(response.status, 209)

        self.assertEqual(
            self.findQuestionTitle(response),
            "<p>No, this is a question</p>")
class TestQuestionRepresentation(TestCaseWithFactory):
    """Test ways of interacting with Question webservice representations."""

    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestQuestionRepresentation, self).setUp()
        with celebrity_logged_in('admin'):
            self.question = self.factory.makeQuestion(
                title="This is a question")
            self.target_name = self.question.target.name

        self.webservice = LaunchpadWebServiceCaller('launchpad-library',
                                                    'salgado-change-anything')
        self.webservice.default_api_version = 'devel'

    def findQuestionTitle(self, response):
        """Find the question title field in an XHTML document fragment."""
        soup = BeautifulSoup(response.body)
        dt = soup.find('dt', text="title")
        dd = dt.findNextSibling('dd')
        return str(dd.contents.pop())

    def test_top_level_question_get(self):
        # The top level question set can be used via the api to get
        # a question by id via redirect without url hacking.
        response = self.webservice.get('/questions/%s' % self.question.id,
                                       'application/xhtml+xml')
        self.assertEqual(response.status, 200)

    def test_GET_xhtml_representation(self):
        # A question's xhtml representation is available on the api.
        response = self.webservice.get(
            '/%s/+question/%d' % (self.target_name, self.question.id),
            'application/xhtml+xml')
        self.assertEqual(response.status, 200)

        self.assertEqual(self.findQuestionTitle(response),
                         "<p>This is a question</p>")

    def test_PATCH_xhtml_representation(self):
        # You can update the question through the api with PATCH.
        new_title = "No, this is a question"

        question_json = self.webservice.get(
            '/%s/+question/%d' %
            (self.target_name, self.question.id)).jsonBody()

        response = self.webservice.patch(
            question_json['self_link'],
            'application/json',
            dumps(dict(title=new_title)),
            headers=dict(accept='application/xhtml+xml'))

        self.assertEqual(response.status, 209)

        self.assertEqual(self.findQuestionTitle(response),
                         "<p>No, this is a question</p>")

    def test_reject(self):
        # A question can be rejected via the API.
        question_url = '/%s/+question/%d' % (self.target_name,
                                             self.question.id)
        response = self.webservice.named_post(question_url,
                                              'reject',
                                              comment='A rejection message')
        self.assertEqual(201, response.status)
        self.assertThat(response.getheader('location'),
                        EndsWith('%s/messages/1' % question_url))
        self.assertEqual(QuestionStatus.INVALID, self.question.status)

    def test_reject_not_answer_contact(self):
        # If the requesting user is not an answer contact, the API returns a
        # suitable error.
        with celebrity_logged_in('admin'):
            random_person = self.factory.makePerson()
        webservice = webservice_for_person(
            random_person, permission=OAuthPermission.WRITE_PUBLIC)
        webservice.default_api_version = 'devel'
        response = webservice.named_post('/%s/+question/%d' %
                                         (self.target_name, self.question.id),
                                         'reject',
                                         comment='A rejection message')
        self.assertEqual(401, response.status)
Exemple #25
0
class PersonSetWebServiceTests(TestCaseWithFactory):

    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(PersonSetWebServiceTests, self).setUp()
        self.webservice = LaunchpadWebServiceCaller('test', None)
        logout()

    def assertReturnsPeople(self, expected_names, path):
        self.assertEqual(expected_names, [
            person['name']
            for person in self.webservice.get(path).jsonBody()['entries']
        ])

    def test_default_content(self):
        # /people lists the 50 people with the most karma, excluding
        # those with no karma at all.
        self.assertEqual(
            4, len(self.webservice.get('/people').jsonBody()['entries']))

    def test_find(self):
        # It's possible to find people by name.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
        self.assertReturnsPeople([person_name],
                                 '/people?ws.op=find&text=%s' % person_name)

    def test_findTeam(self):
        # The search can be restricted to teams.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            team_name = self.factory.makeTeam(name='%s-team' %
                                              person_name).name
        self.assertReturnsPeople(
            [team_name], '/people?ws.op=findTeam&text=%s' % person_name)

    def test_findPerson(self):
        # The search can be restricted to people.
        with admin_logged_in():
            person_name = self.factory.makePerson().name
            self.factory.makeTeam(name='%s-team' % person_name)
        self.assertReturnsPeople(
            [person_name], '/people?ws.op=findPerson&text=%s' % person_name)

    def test_find_by_date(self):
        # Creation date filtering is supported.
        self.assertReturnsPeople(
            [u'bac'], '/people?ws.op=findPerson&text='
            '&created_after=2008-06-27&created_before=2008-07-01')

    def test_getByEmail(self):
        # You can get a person by their email address.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_email = person.preferredemail.email
        self.assertEqual(
            person_name,
            self.webservice.get('/people?ws.op=getByEmail&email=%s' %
                                person_email).jsonBody()['name'])

    def test_getByEmail_checks_format(self):
        # A malformed email address is rejected.
        e = self.assertRaises(
            ValueError,
            self.webservice.get(
                '/people?ws.op=getByEmail&email=foo@').jsonBody)
        # XXX wgrant bug=1088358: This escaping shouldn't be here; it's
        # not HTML.
        self.assertEqual("email: Invalid email &#x27;foo@&#x27;.", e[0])

    def test_getByOpenIDIdentifier(self):
        # You can get a person by their OpenID identifier URL.
        with admin_logged_in():
            person = self.factory.makePerson()
            person_name = person.name
            person_openid = person.account.openid_identifiers.one().identifier
        self.assertEqual(
            person_name,
            self.webservice.get(
                '/people?ws.op=getByOpenIDIdentifier&'
                'identifier=http://openid.launchpad.dev/%%2Bid/%s' %
                person_openid,
                api_version='devel').jsonBody()['name'])
Exemple #26
0
 def test_webhooks_permissions(self):
     webservice = LaunchpadWebServiceCaller()
     response = webservice.get(self.target_url + '/webhooks',
                               api_version='devel')
     self.assertEqual(401, response.status)
     self.assertIn('launchpad.Edit', response.body)
class TestBuilderEntry(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestBuilderEntry, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_security(self):
        # Attributes can only be set by buildd admins.
        builder = self.factory.makeBuilder()
        user = self.factory.makePerson()
        user_webservice = webservice_for_person(
            user, permission=OAuthPermission.WRITE_PUBLIC)
        patch = dumps({'clean_status': 'Cleaning'})
        logout()

        # A normal user is unauthorized.
        response = user_webservice.patch(api_url(builder),
                                         'application/json',
                                         patch,
                                         api_version='devel')
        self.assertEqual(401, response.status)

        # But a buildd admin can set the attribute.
        with admin_logged_in():
            buildd_admins = getUtility(IPersonSet).getByName(
                'launchpad-buildd-admins')
            buildd_admins.addMember(user, buildd_admins.teamowner)
        response = user_webservice.patch(api_url(builder),
                                         'application/json',
                                         patch,
                                         api_version='devel')
        self.assertEqual(209, response.status)
        self.assertEqual('Cleaning', response.jsonBody()['clean_status'])

    def test_exports_processor(self):
        processor = self.factory.makeProcessor('s1')
        builder = self.factory.makeBuilder(processors=[processor])

        logout()
        entry = self.webservice.get(api_url(builder),
                                    api_version='devel').jsonBody()
        self.assertEndsWith(entry['processor_link'], '/+processors/s1')

    def test_getBuildRecords(self):
        builder = self.factory.makeBuilder()
        build = self.factory.makeBinaryPackageBuild(builder=builder)
        build_title = build.title

        logout()
        results = self.webservice.named_get(api_url(builder),
                                            'getBuildRecords',
                                            pocket='Release',
                                            api_version='devel').jsonBody()
        self.assertEqual([build_title],
                         [entry['title'] for entry in results['entries']])
        results = self.webservice.named_get(api_url(builder),
                                            'getBuildRecords',
                                            pocket='Proposed',
                                            api_version='devel').jsonBody()
        self.assertEqual(0, len(results['entries']))
class TestBuildersCollection(TestCaseWithFactory):
    layer = DatabaseFunctionalLayer

    def setUp(self):
        super(TestBuildersCollection, self).setUp()
        self.webservice = LaunchpadWebServiceCaller()

    def test_list(self):
        names = ['bob', 'frog']
        for i in range(3):
            builder = self.factory.makeBuilder()
            self.factory.makeBinaryPackageBuild().queueBuild().markAsBuilding(
                builder)
            names.append(builder.name)
        logout()
        with RequestTimelineCollector() as recorder:
            builders = self.webservice.get('/builders',
                                           api_version='devel').jsonBody()
        self.assertContentEqual(names,
                                [b['name'] for b in builders['entries']])
        self.assertThat(recorder, HasQueryCount(Equals(19)))

    def test_list_with_private_builds(self):
        # Inaccessible private builds aren't linked in builders'
        # current_build fields.
        with admin_logged_in():
            rbpb = self.factory.makeBinaryPackageBuild(
                archive=self.factory.makeArchive(private=True))
            rbpb.queueBuild().markAsBuilding(
                self.factory.makeBuilder(name='restricted'))
            bpb = self.factory.makeBinaryPackageBuild(
                archive=self.factory.makeArchive(private=False))
            bpb.queueBuild().markAsBuilding(
                self.factory.makeBuilder(name='public'))
            bpb_url = canonical_url(bpb, path_only_if_possible=True)
        logout()

        builders = self.webservice.get('/builders',
                                       api_version='devel').jsonBody()
        current_builds = dict(
            (b['name'], b['current_build_link']) for b in builders['entries'])
        self.assertEqual('tag:launchpad.net:2008:redacted',
                         current_builds['restricted'])
        self.assertEqual('http://api.launchpad.dev/devel' + bpb_url,
                         current_builds['public'])

    def test_getBuildQueueSizes(self):
        logout()
        results = self.webservice.named_get('/builders',
                                            'getBuildQueueSizes',
                                            api_version='devel')
        self.assertEqual(['nonvirt', 'virt'],
                         sorted(results.jsonBody().keys()))

    def test_getBuildersForQueue(self):
        g1 = self.factory.makeProcessor('g1')
        quantum = self.factory.makeProcessor('quantum')
        self.factory.makeBuilder(processors=[quantum], name='quantum_builder1')
        self.factory.makeBuilder(processors=[quantum], name='quantum_builder2')
        self.factory.makeBuilder(processors=[quantum],
                                 name='quantum_builder3',
                                 virtualized=False)
        self.factory.makeBuilder(processors=[g1],
                                 name='g1_builder',
                                 virtualized=False)

        logout()
        results = self.webservice.named_get('/builders',
                                            'getBuildersForQueue',
                                            processor=api_url(quantum),
                                            virtualized=True,
                                            api_version='devel').jsonBody()
        self.assertEqual(['quantum_builder1', 'quantum_builder2'],
                         sorted(builder['name']
                                for builder in results['entries']))

    def test_new(self):
        person = self.factory.makePerson()
        badmins = getUtility(IPersonSet).getByName('launchpad-buildd-admins')
        webservice = webservice_for_person(
            person, permission=OAuthPermission.WRITE_PRIVATE)
        args = dict(name='foo',
                    processors=['/+processors/386'],
                    title='foobar',
                    url='http://foo.buildd:8221/',
                    virtualized=False,
                    api_version='devel')

        response = webservice.named_post('/builders', 'new', **args)
        self.assertEqual(401, response.status)

        with admin_logged_in():
            badmins.addMember(person, badmins)
        response = webservice.named_post('/builders', 'new', **args)
        self.assertEqual(201, response.status)

        self.assertEqual('foobar',
                         webservice.get('/builders/foo').jsonBody()['title'])