helpers.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. # Copyright 2008 Canonical Ltd.
  2. # This file is part of launchpadlib.
  3. #
  4. # launchpadlib is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Lesser General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # launchpadlib is distributed in the hope that it will be useful, but
  10. # WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # Lesser General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Lesser General Public
  15. # License along with launchpadlib. If not, see
  16. # <http://www.gnu.org/licenses/>.
  17. """launchpadlib testing helpers."""
  18. __metaclass__ = type
  19. __all__ = [
  20. "BadSaveKeyring",
  21. "fake_keyring",
  22. "FauxSocketModule",
  23. "InMemoryKeyring",
  24. "NoNetworkAuthorizationEngine",
  25. "NoNetworkLaunchpad",
  26. "TestableLaunchpad",
  27. "nopriv_read_nonprivate",
  28. "salgado_read_nonprivate",
  29. "salgado_with_full_permissions",
  30. ]
  31. from contextlib import contextmanager
  32. import launchpadlib
  33. from launchpadlib.launchpad import Launchpad
  34. from launchpadlib.credentials import (
  35. AccessToken,
  36. Credentials,
  37. RequestTokenAuthorizationEngine,
  38. )
  39. missing = object()
  40. def assert_keyring_not_imported():
  41. assert (
  42. getattr(launchpadlib.credentials, "keyring", missing) is missing
  43. ), "During tests the real keyring module should never be imported."
  44. class NoNetworkAuthorizationEngine(RequestTokenAuthorizationEngine):
  45. """An authorization engine that doesn't open a web browser.
  46. You can use this to test the creation of Launchpad objects and the
  47. storing of credentials. You can't use it to interact with the web
  48. service, since it only pretends to authorize its OAuth request tokens.
  49. """
  50. ACCESS_TOKEN_KEY = "access_key:84"
  51. def __init__(self, *args, **kwargs):
  52. super(NoNetworkAuthorizationEngine, self).__init__(*args, **kwargs)
  53. # Set up some instrumentation.
  54. self.request_tokens_obtained = 0
  55. self.access_tokens_obtained = 0
  56. def get_request_token(self, credentials):
  57. """Pretend to get a request token from the server.
  58. We do this by simply returning a static token ID.
  59. """
  60. self.request_tokens_obtained += 1
  61. return "request_token:42"
  62. def make_end_user_authorize_token(self, credentials, request_token):
  63. """Pretend to exchange a request token for an access token.
  64. We do this by simply setting the access_token property.
  65. """
  66. credentials.access_token = AccessToken(
  67. self.ACCESS_TOKEN_KEY, "access_secret:168"
  68. )
  69. self.access_tokens_obtained += 1
  70. class NoNetworkLaunchpad(Launchpad):
  71. """A Launchpad instance for tests with no network access.
  72. It's only useful for making sure that certain methods were called.
  73. It can't be used to interact with the API.
  74. """
  75. def __init__(
  76. self,
  77. credentials,
  78. authorization_engine,
  79. credential_store,
  80. service_root,
  81. cache,
  82. timeout,
  83. proxy_info,
  84. version,
  85. ):
  86. self.credentials = credentials
  87. self.authorization_engine = authorization_engine
  88. self.credential_store = credential_store
  89. self.passed_in_args = dict(
  90. service_root=service_root,
  91. cache=cache,
  92. timeout=timeout,
  93. proxy_info=proxy_info,
  94. version=version,
  95. )
  96. @classmethod
  97. def authorization_engine_factory(cls, *args):
  98. return NoNetworkAuthorizationEngine(*args)
  99. class TestableLaunchpad(Launchpad):
  100. """A base class for talking to the testing root service."""
  101. def __init__(
  102. self,
  103. credentials,
  104. authorization_engine=None,
  105. credential_store=None,
  106. service_root="test_dev",
  107. cache=None,
  108. timeout=None,
  109. proxy_info=None,
  110. version=Launchpad.DEFAULT_VERSION,
  111. ):
  112. """Provide test-friendly defaults.
  113. :param authorization_engine: Defaults to None, since a test
  114. environment can't use an authorization engine.
  115. :param credential_store: Defaults to None, since tests
  116. generally pass in fully-formed Credentials objects.
  117. :param service_root: Defaults to 'test_dev'.
  118. """
  119. super(TestableLaunchpad, self).__init__(
  120. credentials,
  121. authorization_engine,
  122. credential_store,
  123. service_root=service_root,
  124. cache=cache,
  125. timeout=timeout,
  126. proxy_info=proxy_info,
  127. version=version,
  128. )
  129. @contextmanager
  130. def fake_keyring(fake):
  131. """A context manager which injects a testing keyring implementation."""
  132. # The real keyring package should never be imported during tests.
  133. assert_keyring_not_imported()
  134. launchpadlib.credentials.keyring = fake
  135. launchpadlib.credentials.NoKeyringError = RuntimeError
  136. try:
  137. yield
  138. finally:
  139. del launchpadlib.credentials.keyring
  140. del launchpadlib.credentials.NoKeyringError
  141. class FauxSocketModule:
  142. """A socket module replacement that provides a fake hostname."""
  143. def gethostname(self):
  144. return "HOSTNAME"
  145. class BadSaveKeyring:
  146. """A keyring that generates errors when saving passwords."""
  147. def get_password(self, service, username):
  148. return None
  149. def set_password(self, service, username, password):
  150. raise RuntimeError
  151. class InMemoryKeyring:
  152. """A keyring that saves passwords only in memory."""
  153. def __init__(self):
  154. self.data = {}
  155. def set_password(self, service, username, password):
  156. self.data[service, username] = password
  157. def get_password(self, service, username):
  158. return self.data.get((service, username))
  159. class KnownTokens:
  160. """Known access token/secret combinations."""
  161. def __init__(self, token_string, access_secret):
  162. self.token_string = token_string
  163. self.access_secret = access_secret
  164. self.token = AccessToken(token_string, access_secret)
  165. self.credentials = Credentials(
  166. consumer_name="launchpad-library", access_token=self.token
  167. )
  168. def login(
  169. self,
  170. cache=None,
  171. timeout=None,
  172. proxy_info=None,
  173. version=Launchpad.DEFAULT_VERSION,
  174. ):
  175. """Create a Launchpad object using these credentials."""
  176. return TestableLaunchpad(
  177. self.credentials,
  178. cache=cache,
  179. timeout=timeout,
  180. proxy_info=proxy_info,
  181. version=version,
  182. )
  183. salgado_with_full_permissions = KnownTokens("salgado-change-anything", "test")
  184. salgado_read_nonprivate = KnownTokens("salgado-read-nonprivate", "secret")
  185. nopriv_read_nonprivate = KnownTokens("nopriv-read-nonprivate", "mystery")