123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- # Copyright 2010 Canonical Ltd.
- # This file is part of launchpadlib.
- #
- # launchpadlib is free software: you can redistribute it and/or modify it
- # under the terms of the GNU Lesser General Public License as published by the
- # Free Software Foundation, version 3 of the License.
- #
- # launchpadlib is distributed in the hope that it will be useful, but WITHOUT
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
- # for more details.
- #
- # You should have received a copy of the GNU Lesser General Public License
- # along with launchpadlib. If not, see <http://www.gnu.org/licenses/>.
- """Tests for the LaunchpadOAuthAwareHTTP class."""
- from collections import deque
- from json import dumps
- import tempfile
- import unittest
- try:
- from json import JSONDecodeError
- except ImportError:
- JSONDecodeError = ValueError
- from launchpadlib.errors import Unauthorized
- from launchpadlib.credentials import UnencryptedFileCredentialStore
- from launchpadlib.launchpad import (
- Launchpad,
- LaunchpadOAuthAwareHttp,
- )
- from launchpadlib.testing.helpers import NoNetworkAuthorizationEngine
- # The simplest WADL that looks like a representation of the service root.
- SIMPLE_WADL = b"""<?xml version="1.0"?>
- <application xmlns="http://research.sun.com/wadl/2006/10">
- <resources base="http://www.example.com/">
- <resource path="" type="#service-root"/>
- </resources>
- <resource_type id="service-root">
- <method name="GET" id="service-root-get">
- <response>
- <representation href="#service-root-json"/>
- </response>
- </method>
- </resource_type>
- <representation id="service-root-json" mediaType="application/json"/>
- </application>
- """
- # The simplest JSON that looks like a representation of the service root.
- SIMPLE_JSON = dumps({}).encode("utf-8")
- class Response:
- """A fake HTTP response object."""
- def __init__(self, status, content):
- self.status = status
- self.content = content
- class SimulatedResponsesHttp(LaunchpadOAuthAwareHttp):
- """Responds to HTTP requests by shifting responses off a stack."""
- def __init__(self, responses, *args):
- """Constructor.
- :param responses: A list of HttpResponse objects to use
- in response to requests.
- """
- super(SimulatedResponsesHttp, self).__init__(*args)
- self.sent_responses = []
- self.unsent_responses = responses
- self.cache = None
- def _request(self, *args):
- response = self.unsent_responses.popleft()
- self.sent_responses.append(response)
- return self.retry_on_bad_token(response, response.content, *args)
- class SimulatedResponsesLaunchpad(Launchpad):
- # Every Http object generated by this class will return these
- # responses, in order.
- responses = []
- def httpFactory(self, *args):
- return SimulatedResponsesHttp(
- deque(self.responses), self, self.authorization_engine, *args
- )
- @classmethod
- def credential_store_factory(cls, credential_save_failed):
- return UnencryptedFileCredentialStore(
- tempfile.mkstemp()[1], credential_save_failed
- )
- class SimulatedResponsesTestCase(unittest.TestCase):
- """Test cases that give fake responses to launchpad's HTTP requests."""
- def setUp(self):
- """Clear out the list of simulated responses."""
- SimulatedResponsesLaunchpad.responses = []
- self.engine = NoNetworkAuthorizationEngine(
- "http://api.example.com/", "application name"
- )
- def launchpad_with_responses(self, *responses):
- """Use simulated HTTP responses to get a Launchpad object.
- The given Response objects will be sent, in order, in response
- to launchpadlib's requests.
- :param responses: Some number of Response objects.
- :return: The Launchpad object, assuming that errors in the
- simulated requests didn't prevent one from being created.
- """
- SimulatedResponsesLaunchpad.responses = responses
- return SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- class TestAbilityToParseData(SimulatedResponsesTestCase):
- """Test launchpadlib's ability to handle the sample data.
- To create a Launchpad object, two HTTP requests must succeed and
- return usable data: the requests for the WADL and JSON
- representations of the service root. This test shows that the
- minimal data in SIMPLE_WADL and SIMPLE_JSON is good enough to
- create a Launchpad object.
- """
- def test_minimal_data(self):
- """Make sure that launchpadlib can use the minimal data."""
- self.launchpad_with_responses(
- Response(200, SIMPLE_WADL), Response(200, SIMPLE_JSON)
- )
- def test_bad_wadl(self):
- """Show that bad WADL causes an exception."""
- self.assertRaises(
- SyntaxError,
- self.launchpad_with_responses,
- Response(200, b"This is not WADL."),
- Response(200, SIMPLE_JSON),
- )
- def test_bad_json(self):
- """Show that bad JSON causes an exception."""
- self.assertRaises(
- JSONDecodeError,
- self.launchpad_with_responses,
- Response(200, SIMPLE_WADL),
- Response(200, b"This is not JSON."),
- )
- class TestTokenFailureDuringRequest(SimulatedResponsesTestCase):
- """Test access token failures during a request.
- launchpadlib makes two HTTP requests on startup, to get the WADL
- and JSON representations of the service root. If Launchpad
- receives a 401 error during this process, it will acquire a fresh
- access token and try again.
- """
- def test_good_token(self):
- """If our token is good, we never get another one."""
- SimulatedResponsesLaunchpad.responses = [
- Response(200, SIMPLE_WADL),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 1)
- def test_bad_token(self):
- """If our token is bad, we get another one."""
- SimulatedResponsesLaunchpad.responses = [
- Response(401, b"Invalid token."),
- Response(200, SIMPLE_WADL),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 2)
- def test_expired_token(self):
- """If our token is expired, we get another one."""
- SimulatedResponsesLaunchpad.responses = [
- Response(401, b"Expired token."),
- Response(200, SIMPLE_WADL),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 2)
- def test_unknown_token(self):
- """If our token is unknown, we get another one."""
- SimulatedResponsesLaunchpad.responses = [
- Response(401, b"Unknown access token."),
- Response(200, SIMPLE_WADL),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 2)
- def test_delayed_error(self):
- """We get another token no matter when the error happens."""
- SimulatedResponsesLaunchpad.responses = [
- Response(200, SIMPLE_WADL),
- Response(401, b"Expired token."),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 2)
- def test_many_errors(self):
- """We'll keep getting new tokens as long as tokens are the problem."""
- SimulatedResponsesLaunchpad.responses = [
- Response(401, b"Invalid token."),
- Response(200, SIMPLE_WADL),
- Response(401, b"Expired token."),
- Response(401, b"Invalid token."),
- Response(200, SIMPLE_JSON),
- ]
- self.assertEqual(self.engine.access_tokens_obtained, 0)
- SimulatedResponsesLaunchpad.login_with(
- "application name", authorization_engine=self.engine
- )
- self.assertEqual(self.engine.access_tokens_obtained, 4)
- def test_other_unauthorized(self):
- """If the token is not at fault, a 401 error raises an exception."""
- SimulatedResponsesLaunchpad.responses = [
- Response(401, b"Some other error.")
- ]
- self.assertRaises(
- Unauthorized,
- SimulatedResponsesLaunchpad.login_with,
- "application name",
- authorization_engine=self.engine,
- )
|