#!/usr/bin/python # # Copyright (C) 2008 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __author__ = 'api.jscudder (Jeff Scudder)' import atom.http_interface import atom.url class Error(Exception): pass class NoRecordingFound(Error): pass class MockRequest(object): """Holds parameters of an HTTP request for matching against future requests. """ def __init__(self, operation, url, data=None, headers=None): self.operation = operation if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) self.url = url self.data = data self.headers = headers class MockResponse(atom.http_interface.HttpResponse): """Simulates an httplib.HTTPResponse object.""" def __init__(self, body=None, status=None, reason=None, headers=None): if body and hasattr(body, 'read'): self.body = body.read() else: self.body = body if status is not None: self.status = int(status) else: self.status = None self.reason = reason self._headers = headers or {} def read(self): return self.body class MockHttpClient(atom.http_interface.GenericHttpClient): def __init__(self, headers=None, recordings=None, real_client=None): """An HttpClient which responds to request with stored data. The request-response pairs are stored as tuples in a member list named recordings. The MockHttpClient can be switched from replay mode to record mode by setting the real_client member to an instance of an HttpClient which will make real HTTP requests and store the server's response in list of recordings. Args: headers: dict containing HTTP headers which should be included in all HTTP requests. recordings: The initial recordings to be used for responses. This list contains tuples in the form: (MockRequest, MockResponse) real_client: An HttpClient which will make a real HTTP request. The response will be converted into a MockResponse and stored in recordings. """ self.recordings = recordings or [] self.real_client = real_client self.headers = headers or {} def add_response(self, response, operation, url, data=None, headers=None): """Adds a request-response pair to the recordings list. After the recording is added, future matching requests will receive the response. Args: response: MockResponse operation: str url: str data: str, Currently the data is ignored when looking for matching requests. headers: dict of strings: Currently the headers are ignored when looking for matching requests. """ request = MockRequest(operation, url, data=data, headers=headers) self.recordings.append((request, response)) def request(self, operation, url, data=None, headers=None): """Returns a matching MockResponse from the recordings. If the real_client is set, the request will be passed along and the server's response will be added to the recordings and also returned. If there is no match, a NoRecordingFound error will be raised. """ if self.real_client is None: if isinstance(url, (str, unicode)): url = atom.url.parse_url(url) for recording in self.recordings: if recording[0].operation == operation and recording[0].url == url: return recording[1] raise NoRecordingFound('No recodings found for %s %s' % ( operation, url)) else: # There is a real HTTP client, so make the request, and record the # response. response = self.real_client.request(operation, url, data=data, headers=headers) # TODO: copy the headers stored_response = MockResponse(body=response, status=response.status, reason=response.reason) self.add_response(stored_response, operation, url, data=data, headers=headers) return stored_response