PageRenderTime 26ms CodeModel.GetById 12ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 1ms

/atom/mock_service.py

http://radioappz.googlecode.com/
Python | 243 lines | 174 code | 12 blank | 57 comment | 17 complexity | 3832989352a1630fbd83a05df3bdbfe5 MD5 | raw file
  1#!/usr/bin/python
  2#
  3# Copyright (C) 2008 Google Inc.
  4#
  5# Licensed under the Apache License, Version 2.0 (the "License");
  6# you may not use this file except in compliance with the License.
  7# You may obtain a copy of the License at
  8#
  9#      http://www.apache.org/licenses/LICENSE-2.0
 10#
 11# Unless required by applicable law or agreed to in writing, software
 12# distributed under the License is distributed on an "AS IS" BASIS,
 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14# See the License for the specific language governing permissions and
 15# limitations under the License.
 16
 17
 18"""MockService provides CRUD ops. for mocking calls to AtomPub services.
 19
 20  MockService: Exposes the publicly used methods of AtomService to provide
 21      a mock interface which can be used in unit tests.
 22"""
 23
 24import atom.service
 25import pickle
 26
 27
 28__author__ = 'api.jscudder (Jeffrey Scudder)'
 29
 30
 31# Recordings contains pairings of HTTP MockRequest objects with MockHttpResponse objects.
 32recordings = []
 33# If set, the mock service HttpRequest are actually made through this object.
 34real_request_handler = None
 35
 36def ConcealValueWithSha(source):
 37  import sha
 38  return sha.new(source[:-5]).hexdigest()
 39
 40def DumpRecordings(conceal_func=ConcealValueWithSha):
 41  if conceal_func:
 42    for recording_pair in recordings:
 43      recording_pair[0].ConcealSecrets(conceal_func)
 44  return pickle.dumps(recordings)
 45
 46def LoadRecordings(recordings_file_or_string):
 47  if isinstance(recordings_file_or_string, str):
 48    atom.mock_service.recordings =  pickle.loads(recordings_file_or_string)
 49  elif hasattr(recordings_file_or_string, 'read'):
 50    atom.mock_service.recordings = pickle.loads(
 51      recordings_file_or_string.read())
 52
 53def HttpRequest(service, operation, data, uri, extra_headers=None,
 54    url_params=None, escape_params=True, content_type='application/atom+xml'):
 55  """Simulates an HTTP call to the server, makes an actual HTTP request if 
 56  real_request_handler is set.
 57
 58  This function operates in two different modes depending on if 
 59  real_request_handler is set or not. If real_request_handler is not set,
 60  HttpRequest will look in this module's recordings list to find a response
 61  which matches the parameters in the function call. If real_request_handler
 62  is set, this function will call real_request_handler.HttpRequest, add the
 63  response to the recordings list, and respond with the actual response.
 64
 65  Args:
 66    service: atom.AtomService object which contains some of the parameters
 67        needed to make the request. The following members are used to
 68        construct the HTTP call: server (str), additional_headers (dict),
 69        port (int), and ssl (bool).
 70    operation: str The HTTP operation to be performed. This is usually one of
 71        'GET', 'POST', 'PUT', or 'DELETE'
 72    data: ElementTree, filestream, list of parts, or other object which can be
 73        converted to a string.
 74        Should be set to None when performing a GET or PUT.
 75        If data is a file-like object which can be read, this method will read
 76        a chunk of 100K bytes at a time and send them.
 77        If the data is a list of parts to be sent, each part will be evaluated
 78        and sent.
 79    uri: The beginning of the URL to which the request should be sent.
 80        Examples: '/', '/base/feeds/snippets',
 81        '/m8/feeds/contacts/default/base'
 82    extra_headers: dict of strings. HTTP headers which should be sent
 83        in the request. These headers are in addition to those stored in
 84        service.additional_headers.
 85    url_params: dict of strings. Key value pairs to be added to the URL as
 86        URL parameters. For example {'foo':'bar', 'test':'param'} will
 87        become ?foo=bar&test=param.
 88    escape_params: bool default True. If true, the keys and values in
 89        url_params will be URL escaped when the form is constructed
 90        (Special characters converted to %XX form.)
 91    content_type: str The MIME type for the data being sent. Defaults to
 92        'application/atom+xml', this is only used if data is set.
 93  """
 94  full_uri = atom.service.BuildUri(uri, url_params, escape_params)
 95  (server, port, ssl, uri) = atom.service.ProcessUrl(service, uri)
 96  current_request = MockRequest(operation, full_uri, host=server, ssl=ssl, 
 97      data=data, extra_headers=extra_headers, url_params=url_params, 
 98      escape_params=escape_params, content_type=content_type)
 99  # If the request handler is set, we should actually make the request using 
100  # the request handler and record the response to replay later.
101  if real_request_handler:
102    response = real_request_handler.HttpRequest(service, operation, data, uri,
103        extra_headers=extra_headers, url_params=url_params, 
104        escape_params=escape_params, content_type=content_type)
105    # TODO: need to copy the HTTP headers from the real response into the
106    # recorded_response.
107    recorded_response = MockHttpResponse(body=response.read(), 
108        status=response.status, reason=response.reason)
109    # Insert a tuple which maps the request to the response object returned
110    # when making an HTTP call using the real_request_handler.
111    recordings.append((current_request, recorded_response))
112    return recorded_response
113  else:
114    # Look through available recordings to see if one matches the current 
115    # request.
116    for request_response_pair in recordings:
117      if request_response_pair[0].IsMatch(current_request):
118        return request_response_pair[1]
119  return None
120
121
122class MockRequest(object):
123  """Represents a request made to an AtomPub server.
124  
125  These objects are used to determine if a client request matches a recorded
126  HTTP request to determine what the mock server's response will be. 
127  """
128
129  def __init__(self, operation, uri, host=None, ssl=False, port=None, 
130      data=None, extra_headers=None, url_params=None, escape_params=True,
131      content_type='application/atom+xml'):
132    """Constructor for a MockRequest
133    
134    Args:
135      operation: str One of 'GET', 'POST', 'PUT', or 'DELETE' this is the
136          HTTP operation requested on the resource.
137      uri: str The URL describing the resource to be modified or feed to be
138          retrieved. This should include the protocol (http/https) and the host
139          (aka domain). For example, these are some valud full_uris:
140          'http://example.com', 'https://www.google.com/accounts/ClientLogin'
141      host: str (optional) The server name which will be placed at the 
142          beginning of the URL if the uri parameter does not begin with 'http'.
143          Examples include 'example.com', 'www.google.com', 'www.blogger.com'.
144      ssl: boolean (optional) If true, the request URL will begin with https 
145          instead of http.
146      data: ElementTree, filestream, list of parts, or other object which can be
147          converted to a string. (optional)
148          Should be set to None when performing a GET or PUT.
149          If data is a file-like object which can be read, the constructor 
150          will read the entire file into memory. If the data is a list of 
151          parts to be sent, each part will be evaluated and stored.
152      extra_headers: dict (optional) HTTP headers included in the request.
153      url_params: dict (optional) Key value pairs which should be added to 
154          the URL as URL parameters in the request. For example uri='/', 
155          url_parameters={'foo':'1','bar':'2'} could become '/?foo=1&bar=2'.
156      escape_params: boolean (optional) Perform URL escaping on the keys and 
157          values specified in url_params. Defaults to True.
158      content_type: str (optional) Provides the MIME type of the data being 
159          sent.
160    """
161    self.operation = operation
162    self.uri = _ConstructFullUrlBase(uri, host=host, ssl=ssl)
163    self.data = data
164    self.extra_headers = extra_headers
165    self.url_params = url_params or {}
166    self.escape_params = escape_params
167    self.content_type = content_type
168
169  def ConcealSecrets(self, conceal_func):
170    """Conceal secret data in this request."""
171    if self.extra_headers.has_key('Authorization'):
172      self.extra_headers['Authorization'] = conceal_func(
173        self.extra_headers['Authorization'])
174
175  def IsMatch(self, other_request):
176    """Check to see if the other_request is equivalent to this request.
177    
178    Used to determine if a recording matches an incoming request so that a
179    recorded response should be sent to the client.
180
181    The matching is not exact, only the operation and URL are examined 
182    currently.
183
184    Args:
185      other_request: MockRequest The request which we want to check this
186          (self) MockRequest against to see if they are equivalent.
187    """
188    # More accurate matching logic will likely be required.
189    return (self.operation == other_request.operation and self.uri == 
190        other_request.uri)
191
192
193def _ConstructFullUrlBase(uri, host=None, ssl=False):
194  """Puts URL components into the form http(s)://full.host.strinf/uri/path
195  
196  Used to construct a roughly canonical URL so that URLs which begin with 
197  'http://example.com/' can be compared to a uri of '/' when the host is 
198  set to 'example.com'
199
200  If the uri contains 'http://host' already, the host and ssl parameters
201  are ignored.
202
203  Args:
204    uri: str The path component of the URL, examples include '/'
205    host: str (optional) The host name which should prepend the URL. Example:
206        'example.com'
207    ssl: boolean (optional) If true, the returned URL will begin with https
208        instead of http.
209
210  Returns:
211    String which has the form http(s)://example.com/uri/string/contents
212  """
213  if uri.startswith('http'):
214    return uri
215  if ssl:
216    return 'https://%s%s' % (host, uri)
217  else:
218    return 'http://%s%s' % (host, uri)
219
220
221class MockHttpResponse(object):
222  """Returned from MockService crud methods as the server's response."""
223
224  def __init__(self, body=None, status=None, reason=None, headers=None):
225    """Construct a mock HTTPResponse and set members.
226
227    Args:
228      body: str (optional) The HTTP body of the server's response. 
229      status: int (optional) 
230      reason: str (optional)
231      headers: dict (optional)
232    """
233    self.body = body
234    self.status = status
235    self.reason = reason
236    self.headers = headers or {}
237
238  def read(self):
239    return self.body
240
241  def getheader(self, header_name):
242    return self.headers[header_name]
243