/atom/mock_http.py
Python | 132 lines | 85 code | 16 blank | 31 comment | 12 complexity | 6494631e5d70033d33ccb0205c8dcc13 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__author__ = 'api.jscudder (Jeff Scudder)' 19 20 21import atom.http_interface 22import atom.url 23 24 25class Error(Exception): 26 pass 27 28 29class NoRecordingFound(Error): 30 pass 31 32 33class MockRequest(object): 34 """Holds parameters of an HTTP request for matching against future requests. 35 """ 36 def __init__(self, operation, url, data=None, headers=None): 37 self.operation = operation 38 if isinstance(url, (str, unicode)): 39 url = atom.url.parse_url(url) 40 self.url = url 41 self.data = data 42 self.headers = headers 43 44 45class MockResponse(atom.http_interface.HttpResponse): 46 """Simulates an httplib.HTTPResponse object.""" 47 def __init__(self, body=None, status=None, reason=None, headers=None): 48 if body and hasattr(body, 'read'): 49 self.body = body.read() 50 else: 51 self.body = body 52 if status is not None: 53 self.status = int(status) 54 else: 55 self.status = None 56 self.reason = reason 57 self._headers = headers or {} 58 59 def read(self): 60 return self.body 61 62 63class MockHttpClient(atom.http_interface.GenericHttpClient): 64 def __init__(self, headers=None, recordings=None, real_client=None): 65 """An HttpClient which responds to request with stored data. 66 67 The request-response pairs are stored as tuples in a member list named 68 recordings. 69 70 The MockHttpClient can be switched from replay mode to record mode by 71 setting the real_client member to an instance of an HttpClient which will 72 make real HTTP requests and store the server's response in list of 73 recordings. 74 75 Args: 76 headers: dict containing HTTP headers which should be included in all 77 HTTP requests. 78 recordings: The initial recordings to be used for responses. This list 79 contains tuples in the form: (MockRequest, MockResponse) 80 real_client: An HttpClient which will make a real HTTP request. The 81 response will be converted into a MockResponse and stored in 82 recordings. 83 """ 84 self.recordings = recordings or [] 85 self.real_client = real_client 86 self.headers = headers or {} 87 88 def add_response(self, response, operation, url, data=None, headers=None): 89 """Adds a request-response pair to the recordings list. 90 91 After the recording is added, future matching requests will receive the 92 response. 93 94 Args: 95 response: MockResponse 96 operation: str 97 url: str 98 data: str, Currently the data is ignored when looking for matching 99 requests. 100 headers: dict of strings: Currently the headers are ignored when 101 looking for matching requests. 102 """ 103 request = MockRequest(operation, url, data=data, headers=headers) 104 self.recordings.append((request, response)) 105 106 def request(self, operation, url, data=None, headers=None): 107 """Returns a matching MockResponse from the recordings. 108 109 If the real_client is set, the request will be passed along and the 110 server's response will be added to the recordings and also returned. 111 112 If there is no match, a NoRecordingFound error will be raised. 113 """ 114 if self.real_client is None: 115 if isinstance(url, (str, unicode)): 116 url = atom.url.parse_url(url) 117 for recording in self.recordings: 118 if recording[0].operation == operation and recording[0].url == url: 119 return recording[1] 120 raise NoRecordingFound('No recodings found for %s %s' % ( 121 operation, url)) 122 else: 123 # There is a real HTTP client, so make the request, and record the 124 # response. 125 response = self.real_client.request(operation, url, data=data, 126 headers=headers) 127 # TODO: copy the headers 128 stored_response = MockResponse(body=response, status=response.status, 129 reason=response.reason) 130 self.add_response(stored_response, operation, url, data=data, 131 headers=headers) 132 return stored_response