18
16
from grackle.client import (
20
UnsupportedDisplayType,
25
def threaded_messages(messages):
29
for message in messages:
30
if message.get('in_reply_to') is None:
31
threads[message['message_id']] = [message]
34
pending.append(message)
35
for message in pending:
36
threads[message['in_reply_to']].append(message)
37
return threads.values()
41
"""A memory-backed message store."""
43
def __init__(self, messages):
45
self.messages = messages
47
def get_messages(self, archive_id, query_string):
48
"""Return matching messages.
50
:param archive_id: The archive to retrieve from.
51
:param query_string: Contains 'parameters', which is a JSON-format
52
string describing parameters.
54
query = parse_qs(query_string)
55
parameters = simplejson.loads(query['parameters'][0])
56
order = parameters.get('order')
57
messages = self.messages[archive_id]
59
if order not in SUPPORTED_ORDERS:
60
raise UnsupportedOrder
61
elif order.startswith('thread_'):
62
threaded = threaded_messages(messages)
64
if order == 'thread_subject':
65
threaded.sort(key=lambda t: t[0]['subject'])
66
if order == 'thread_oldest':
67
threaded.sort(key=lambda t: min(m['date'] for m in t))
68
if order == 'thread_newest':
69
threaded.sort(key=lambda t: max(m['date'] for m in t))
70
for thread in threaded:
71
messages.extend(thread)
73
messages.sort(key=lambda m: m[order])
74
display_type = parameters.get('display_type', 'all')
75
if display_type not in SUPPORTED_DISPLAY_TYPES:
76
raise UnsupportedDisplayType
78
for message in messages:
79
if (not parameters['include_hidden']
80
and message.get('hidden', False)):
83
if ('message_ids' in parameters
84
and message['message_id'] not in parameters['message_ids']):
86
message = dict(message)
87
if 'headers' in parameters:
89
(k, v) for k, v in message['headers'].iteritems()
90
if k in parameters['headers'])
91
message['headers'] = headers
92
max_body = parameters.get('max_body_length')
93
if max_body is not None:
94
message['body'] = message['body'][:max_body]
95
new_messages.append(message)
96
messages = new_messages
97
limit = parameters.get('limit', 100)
98
memo = parameters.get('memo')
99
message_id_indices = dict(
100
(m['message_id'], idx) for idx, m in enumerate(messages))
104
start = message_id_indices[memo.encode('rot13')]
106
previous_memo = messages[start - 1]['message_id'].encode('rot13')
109
end = min(start + limit, len(messages))
110
if end < len(messages):
111
next_memo = messages[end]['message_id'].encode('rot13')
114
messages = messages[start:end]
117
'messages': messages,
118
'next_memo': next_memo,
119
'previous_memo': previous_memo
124
class ForkedFakeService:
125
"""A Grackle service fake, as a ContextManager."""
127
def __init__(self, port, messages=None, write_logs=False):
130
:param port: The tcp port to use.
131
:param messages: A dict of lists of dicts representing messages. The
132
outer dict represents the archive, the list represents the list of
133
messages for that archive.
134
:param write_logs: If true, log messages will be written to stdout.
23
def __init__(self, port, messages=None):
141
self.messages = messages
26
self.messages = messages
142
27
self.read_end, self.write_end = os.pipe()
143
self.write_logs = write_logs
146
def from_client(client, messages=None):
147
"""Instantiate a ForkedFakeService from the client.
149
:param port: The client to provide service for.
150
:param messages: A dict of lists of dicts representing messages. The
151
outer dict represents the archive, the list represents the list of
152
messages for that archive.
154
return ForkedFakeService(client.port, messages)
156
29
def is_ready(self):
157
"""Tell the parent process that the server is ready for writes."""
158
30
os.write(self.write_end, 'asdf')
160
32
def __enter__(self):
163
Fork and start a server in the child. Return when the server is ready
167
35
self.start_server()
213
59
self.send_error(httplib.BAD_REQUEST)
216
"""Retrieve a list of messages on GET."""
217
62
scheme, netloc, path, params, query_string, fragments = (
218
63
urlparse(self.path))
219
parts = path.split('/')
220
if parts[1] == 'archive':
222
response = self.server.store.get_messages(
223
parts[2], query_string)
224
self.send_response(httplib.OK)
226
self.wfile.write(simplejson.dumps(response))
227
except UnsupportedOrder:
228
self.send_response(httplib.BAD_REQUEST)
229
self.wfile.write('Unsupported order')
232
def log_message(self, format, *args):
233
"""Override log_message to use standard Python logging."""
234
message = "%s - - [%s] %s\n" % (
235
self.address_string(), self.log_date_time_string(), format % args)
236
self.logger.info(message)
64
archive = os.path.split(path)[1]
65
query = parse_qs(query_string)
66
parameters = simplejson.loads(query['parameters'][0])
67
self.send_response(httplib.OK)
69
messages = [m for m in self.server.messages[archive] if 'message_ids'
70
not in parameters or m['message-id'] in
71
parameters['message_ids']]
77
self.wfile.write(simplejson.dumps(response))
80
def fake_grackle_service(client, messages=None):
83
return ForkedFake(client.port, messages)
239
86
class TestPutMessage(TestCase):
241
88
def test_put_message(self):
242
89
client = GrackleClient('localhost', 8436)
243
with ForkedFakeService.from_client(client):
90
with fake_grackle_service(client):
244
91
client.put_message('arch1', 'asdf', StringIO('This is a message'))
245
92
with ExpectedException(Exception, 'wtf'):
246
93
client.put_message('arch1', 'asdf',
250
97
class TestGetMessages(TestCase):
252
def assertIDOrder(self, ids, messages):
253
self.assertEqual(ids, [m['message_id'] for m in messages])
255
def assertMessageIDs(self, ids, messages):
257
sorted(ids), sorted(messages, key=lambda m: m['message_id']))
259
99
def test_get_messages(self):
260
100
client = GrackleClient('localhost', 8435)
261
with ForkedFakeService.from_client(client,
101
with fake_grackle_service(client,
263
[{'message_id': 'foo'},
264
{'message_id': 'bar'}]}):
103
[{'message-id': 'foo'},
104
{'message-id': 'bar'}]}):
265
105
response = client.get_messages('baz')
266
self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
106
self.assertEqual(['bar', 'foo'], sorted(m['message-id'] for m in
267
107
response['messages']))
268
108
self.assertIs(None, response['next_memo'])
269
109
self.assertIs(None, response['previous_memo'])
271
111
def test_get_messages_by_id(self):
272
112
client = GrackleClient('localhost', 8437)
273
with ForkedFakeService.from_client(client,
113
with fake_grackle_service(client,
275
[{'message_id': 'foo'},
276
{'message_id': 'bar'}]}):
115
[{'message-id': 'foo'},
116
{'message-id': 'bar'}]}):
277
117
response = client.get_messages('baz', message_ids=['foo'])
278
118
message, = response['messages']
279
self.assertEqual('foo', message['message_id'])
281
def test_get_messages_batching(self):
282
client = GrackleClient('localhost', 8438)
283
with ForkedFakeService.from_client(client,
285
[{'message_id': 'foo'},
286
{'message_id': 'bar'}]}):
287
response = client.get_messages('baz', limit=1)
288
self.assertEqual(1, len(response['messages']))
289
messages = response['messages']
290
response = client.get_messages(
291
'baz', limit=1, memo=response['next_memo'])
292
self.assertEqual(1, len(response['messages']))
293
messages.extend(response['messages'])
294
self.assertMessageIDs(['foo', 'bar'], messages)
296
def get_messages_member_order_test(self, key):
297
client = GrackleClient('localhost', 8439)
298
with ForkedFakeService.from_client(client,
299
{'baz': [{'message_id': 'foo', key: '2011-03-25'},
300
{'message_id': 'bar', key: '2011-03-24'}]}):
301
response = client.get_messages('baz')
302
self.assertIDOrder(['foo', 'bar'], response['messages'])
303
response = client.get_messages('baz', order=key)
304
self.assertIDOrder(['bar', 'foo'], response['messages'])
306
def test_get_messages_date_order(self):
307
self.get_messages_member_order_test('date')
309
def test_get_messages_author_order(self):
310
self.get_messages_member_order_test('author')
312
def test_get_messages_subject_order(self):
313
self.get_messages_member_order_test('subject')
315
def test_get_messages_thread_subject_order(self):
316
client = GrackleClient('localhost', 8439)
317
with ForkedFakeService.from_client(client, {'baz': [
318
{'message_id': 'bar', 'subject': 'y'},
319
{'message_id': 'qux', 'subject': 'z'},
320
{'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
322
response = client.get_messages('baz')
323
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
324
response = client.get_messages('baz', order='subject')
325
self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
326
response = client.get_messages('baz', order='thread_subject')
327
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
329
def test_get_messages_thread_oldest_order(self):
330
client = GrackleClient('localhost', 8439)
331
with ForkedFakeService.from_client(client, {'baz': [
332
{'message_id': 'bar', 'date': 'x'},
333
{'message_id': 'qux', 'date': 'z'},
334
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
336
response = client.get_messages('baz')
337
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
338
response = client.get_messages('baz', order='date')
339
self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
340
response = client.get_messages('baz', order='thread_oldest')
341
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
343
def test_get_messages_thread_newest_order(self):
344
client = GrackleClient('localhost', 8439)
345
with ForkedFakeService.from_client(client, {'baz': [
346
{'message_id': 'bar', 'date': 'x'},
347
{'message_id': 'qux', 'date': 'w'},
348
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
349
{'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
351
response = client.get_messages('baz', order='date')
353
['qux', 'bar', 'foo', 'baz'], response['messages'])
354
response = client.get_messages('baz', order='thread_newest')
356
['bar', 'foo', 'qux', 'baz'], response['messages'])
358
def test_get_messages_unsupported_order(self):
359
client = GrackleClient('localhost', 8439)
360
with ForkedFakeService.from_client(client,
361
{'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
362
{'message_id': 'bar', 'date': '2011-03-24'}]}):
363
with ExpectedException(UnsupportedOrder, ''):
364
client.get_messages('baz', order='nonsense')
366
def test_get_messages_headers_no_headers(self):
367
client = GrackleClient('localhost', 8440)
368
with ForkedFakeService.from_client(client,
370
{'message_id': 'foo'}
372
response = client.get_messages('baz', headers=[
373
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
374
first_message = response['messages'][0]
375
self.assertEqual('foo', first_message['message_id'])
376
self.assertEqual({}, first_message['headers'])
378
def test_get_messages_headers_exclude_headers(self):
379
client = GrackleClient('localhost', 8441)
380
with ForkedFakeService.from_client(client,
382
{'message_id': 'foo', 'headers': {'From': 'me'}}
384
response = client.get_messages('baz', headers=[
385
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
386
first_message = response['messages'][0]
387
self.assertEqual('foo', first_message['message_id'])
388
self.assertEqual({}, first_message['headers'])
390
def test_get_messages_headers_include_headers(self):
391
client = GrackleClient('localhost', 8442)
392
with ForkedFakeService.from_client(client,
394
{'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
396
response = client.get_messages('baz', headers=[
398
first_message = response['messages'][0]
399
self.assertEqual('foo', first_message['message_id'])
400
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
402
def test_get_messages_max_body_length(self):
403
client = GrackleClient('localhost', 8443)
404
with ForkedFakeService.from_client(client,
406
{'message_id': 'foo', 'body': u'abcdefghi'}
408
response = client.get_messages('baz', max_body_length=3)
409
first_message = response['messages'][0]
410
self.assertEqual('abc', first_message['body'])
412
def test_include_hidden(self):
413
client = GrackleClient('localhost', 8444)
414
with ForkedFakeService.from_client(client,
416
{'message_id': 'foo', 'hidden': True},
417
{'message_id': 'bar', 'hidden': False}
419
response = client.get_messages('baz', include_hidden=True)
420
self.assertMessageIDs(['bar', 'foo'], response['messages'])
421
response = client.get_messages('baz', include_hidden=False)
422
self.assertMessageIDs(['bar'], response['messages'])
424
def test_display_type_unknown_value(self):
425
client = GrackleClient('localhost', 8445)
426
with ForkedFakeService.from_client(client,
428
{'message_id': 'foo', 'body': u'abcdefghi'}
430
with ExpectedException(UnsupportedDisplayType, ''):
431
client.get_messages('baz', display_type='unknown')
119
self.assertEqual('foo', message['message-id'])