36
34
return threads.values()
40
"""A memory-backed message store."""
42
def __init__(self, messages):
39
def __init__(self, port, messages=None):
44
42
self.messages = messages
46
def get_messages(self, archive_id, query_string):
47
"""Return matching messages.
49
:param archive_id: The archive to retrieve from.
50
:param query_string: Contains 'parameters', which is a JSON-format
51
string describing parameters.
43
self.read_end, self.write_end = os.pipe()
46
os.write(self.write_end, 'asdf')
53
os.read(self.read_end, 1)
56
def start_server(self):
57
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
58
service.messages = self.messages
60
service.serve_forever()
62
def __exit__(self, exc_type, exc_val, traceback):
63
os.kill(self.pid, SIGKILL)
66
SUPPORTED_ORDERS = set(
67
['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
71
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
74
message = self.rfile.read(int(self.headers['content-length']))
75
if message == 'This is a message':
76
self.send_response(httplib.CREATED)
80
self.send_error(httplib.BAD_REQUEST)
83
scheme, netloc, path, params, query_string, fragments = (
85
archive = os.path.split(path)[1]
53
86
query = parse_qs(query_string)
54
87
parameters = simplejson.loads(query['parameters'][0])
55
88
order = parameters.get('order')
56
messages = self.messages[archive_id]
89
messages = self.server.messages[archive]
57
90
if order is not None :
58
91
if order not in SUPPORTED_ORDERS:
59
raise UnsupportedOrder
92
self.send_response(httplib.BAD_REQUEST)
93
self.wfile.write('Unsupported order')
60
95
elif order.startswith('thread_'):
61
96
threaded = threaded_messages(messages)
70
105
messages.extend(thread)
72
107
messages.sort(key=lambda m: m[order])
74
for message in messages:
76
not parameters['include_hidden']
77
and message.get('hidden', False)):
80
if ('message_ids' in parameters and
81
message['message_id'] not in parameters['message_ids']):
83
message = dict(message)
84
if 'headers' in parameters:
86
(k, v) for k, v in message['headers'].iteritems()
87
if k in parameters['headers'])
88
message['headers'] = headers
89
max_body = parameters.get('max_body_length')
90
if max_body is not None:
91
message['body'] = message['body'][:max_body]
92
new_messages.append(message)
93
messages = new_messages
108
messages = [m for m in messages
109
if 'message_ids' not in parameters or
110
m['message_id'] in parameters['message_ids']]
111
self.send_response(httplib.OK)
94
113
limit = parameters.get('limit', 100)
95
114
memo = parameters.get('memo')
96
115
message_id_indices = dict(
111
130
messages = messages[start:end]
114
132
'messages': messages,
115
133
'next_memo': next_memo,
116
134
'previous_memo': previous_memo
123
"""A Grackle service fake, as a ContextManager."""
125
def __init__(self, port, messages=None, write_logs=False):
127
:param port: The tcp port to use
128
:param messages: A dict of lists of dicts representing messages. The
129
outer dict represents the archive, the list represents the list of
130
messages for that archive.
131
:param write_logs: If true, log messages will be written to stdout.
138
self.messages = messages
139
self.read_end, self.write_end = os.pipe()
140
self.write_logs = write_logs
143
def from_client(client, messages=None):
144
"""Instantiate a ForkedFake from the client.
146
:param port: The client to provide service for.
147
:param messages: A dict of lists of dicts representing messages. The
148
outer dict represents the archive, the list represents the list of
149
messages for that archive.
151
return ForkedFake(client.port, messages)
154
"""Tell the parent process that the server is ready for writes."""
155
os.write(self.write_end, 'asdf')
160
Fork and start a server in the child. Return when the server is ready
166
os.read(self.read_end, 1)
169
def start_server(self):
170
"""Start the HTTP server."""
171
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
172
service.store = GrackleStore(self.messages)
173
for archive_id, messages in service.store.messages.iteritems():
174
for message in messages:
175
message.setdefault('headers', {})
179
stream=sys.stderr, level=logging.INFO)
180
service.serve_forever()
182
def __exit__(self, exc_type, exc_val, traceback):
183
os.kill(self.pid, SIGKILL)
186
SUPPORTED_ORDERS = set(
187
['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
191
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
192
"""A request handler that forwards to server.store."""
194
def __init__(self, *args, **kwargs):
195
"""Constructor. Sets up logging."""
196
self.logger = logging.getLogger('http')
197
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
200
"""Create a message on POST."""
201
message = self.rfile.read(int(self.headers['content-length']))
202
if message == 'This is a message':
203
self.send_response(httplib.CREATED)
207
self.send_error(httplib.BAD_REQUEST)
210
"""Retrieve a list of messages on GET."""
211
scheme, netloc, path, params, query_string, fragments = (
213
parts = path.split('/')
214
if parts[1] == 'archive':
216
response = self.server.store.get_messages(
217
parts[2], query_string)
218
self.send_response(httplib.OK)
220
self.wfile.write(simplejson.dumps(response))
221
except UnsupportedOrder:
222
self.send_response(httplib.BAD_REQUEST)
223
self.wfile.write('Unsupported order')
226
def log_message(self, format, *args):
227
"""Override log_message to use standard Python logging."""
228
message = "%s - - [%s] %s\n" % (
229
self.address_string(), self.log_date_time_string(), format%args)
230
self.logger.info(message)
136
self.wfile.write(simplejson.dumps(response))
139
def fake_grackle_service(client, messages=None):
142
return ForkedFake(client.port, messages)
233
145
class TestPutMessage(TestCase):
235
147
def test_put_message(self):
236
148
client = GrackleClient('localhost', 8436)
237
with ForkedFake.from_client(client):
149
with fake_grackle_service(client):
238
150
client.put_message('arch1', 'asdf', StringIO('This is a message'))
239
151
with ExpectedException(Exception, 'wtf'):
240
152
client.put_message('arch1', 'asdf',
352
264
def test_get_messages_unsupported_order(self):
353
265
client = GrackleClient('localhost', 8439)
354
with ForkedFake.from_client(client,
266
with fake_grackle_service(client,
355
267
{'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
356
268
{'message_id': 'bar', 'date': '2011-03-24'}]}):
357
with ExpectedException(UnsupportedOrder, ''):
269
with ExpectedException(UnsupportedOrder):
358
270
client.get_messages('baz', order='nonsense')
360
def test_get_messages_headers_no_headers(self):
361
client = GrackleClient('localhost', 8440)
362
with ForkedFake.from_client(client,
364
{'message_id': 'foo'}
366
response = client.get_messages('baz', headers=[
367
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
368
first_message = response['messages'][0]
369
self.assertEqual('foo', first_message['message_id'])
370
self.assertEqual({}, first_message['headers'])
372
def test_get_messages_headers_exclude_headers(self):
373
client = GrackleClient('localhost', 8441)
374
with ForkedFake.from_client(client,
376
{'message_id': 'foo', 'headers': {'From': 'me'}}
378
response = client.get_messages('baz', headers=[
379
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
380
first_message = response['messages'][0]
381
self.assertEqual('foo', first_message['message_id'])
382
self.assertEqual({}, first_message['headers'])
384
def test_get_messages_headers_include_headers(self):
385
client = GrackleClient('localhost', 8442)
386
with ForkedFake.from_client(client,
388
{'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
390
response = client.get_messages('baz', headers=[
392
first_message = response['messages'][0]
393
self.assertEqual('foo', first_message['message_id'])
394
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
396
def test_get_messages_max_body_length(self):
397
client = GrackleClient('localhost', 8443)
398
with ForkedFake.from_client(client,
400
{'message_id': 'foo', 'body': u'abcdefghi'}
402
response = client.get_messages('baz', max_body_length=3)
403
first_message = response['messages'][0]
404
self.assertEqual('abc', first_message['body'])
406
def test_include_hidden(self):
407
client = GrackleClient('localhost', 8444)
408
with ForkedFake.from_client(client,
410
{'message_id': 'foo', 'hidden': True},
411
{'message_id': 'bar', 'hidden': False}
413
response = client.get_messages('baz', include_hidden=True)
414
self.assertMessageIDs(['bar', 'foo'], response['messages'])
415
response = client.get_messages('baz', include_hidden=False)
416
self.assertMessageIDs(['bar'], response['messages'])