3
3
BaseHTTPRequestHandler,
8
7
from signal import SIGKILL
10
8
from StringIO import StringIO
12
9
from unittest import TestCase
13
from urlparse import urlparse
14
from urlparse import parse_qs
16
11
from testtools import ExpectedException
18
13
from grackle.client import (
24
def threaded_messages(messages):
28
for message in messages:
29
if message.get('in_reply_to') is None:
30
threads[message['message_id']] = [message]
33
pending.append(message)
34
for message in pending:
35
threads[message['in_reply_to']].append(message)
36
return threads.values()
40
"""A memory-backed message store."""
42
def __init__(self, messages):
44
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.
53
query = parse_qs(query_string)
54
parameters = simplejson.loads(query['parameters'][0])
55
order = parameters.get('order')
56
messages = self.messages[archive_id]
57
if order is not None :
58
if order not in SUPPORTED_ORDERS:
59
raise UnsupportedOrder
60
elif order.startswith('thread_'):
61
threaded = threaded_messages(messages)
63
if order == 'thread_subject':
64
threaded.sort(key=lambda t: t[0]['subject'])
65
if order == 'thread_oldest':
66
threaded.sort(key=lambda t: min(m['date'] for m in t))
67
if order == 'thread_newest':
68
threaded.sort(key=lambda t: max(m['date'] for m in t))
69
for thread in threaded:
70
messages.extend(thread)
72
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
94
limit = parameters.get('limit', 100)
95
memo = parameters.get('memo')
96
message_id_indices = dict(
97
(m['message_id'], idx) for idx, m in enumerate(messages))
101
start = message_id_indices[memo.encode('rot13')]
103
previous_memo = messages[start - 1]['message_id'].encode('rot13')
106
end = min(start + limit, len(messages))
107
if end < len(messages):
108
next_memo = messages[end]['message_id'].encode('rot13')
111
messages = messages[start:end]
114
'messages': messages,
115
'next_memo': next_memo,
116
'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.
20
def __init__(self, port, messages=None):
138
self.messages = messages
23
self.messages = messages
139
24
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)
153
26
def is_ready(self):
154
"""Tell the parent process that the server is ready for writes."""
155
27
os.write(self.write_end, 'asdf')
157
29
def __enter__(self):
160
Fork and start a server in the child. Return when the server is ready
164
32
self.start_server()
244
76
class TestGetMessages(TestCase):
246
def assertIDOrder(self, ids, messages):
247
self.assertEqual(ids, [m['message_id'] for m in messages])
249
def assertMessageIDs(self, ids, messages):
251
sorted(ids), sorted(messages, key=lambda m:m['message_id']))
253
78
def test_get_messages(self):
254
79
client = GrackleClient('localhost', 8435)
255
with ForkedFake.from_client(client,
257
[{'message_id': 'foo'},
258
{'message_id': 'bar'}]}):
259
response = client.get_messages('baz')
260
self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
261
response['messages']))
262
self.assertIs(None, response['next_memo'])
263
self.assertIs(None, response['previous_memo'])
265
def test_get_messages_by_id(self):
266
client = GrackleClient('localhost', 8437)
267
with ForkedFake.from_client(client,
269
[{'message_id': 'foo'},
270
{'message_id': 'bar'}]}):
271
response = client.get_messages('baz', message_ids=['foo'])
272
message, = response['messages']
273
self.assertEqual('foo', message['message_id'])
275
def test_get_messages_batching(self):
276
client = GrackleClient('localhost', 8438)
277
with ForkedFake.from_client(client,
279
[{'message_id': 'foo'},
280
{'message_id': 'bar'}]}):
281
response = client.get_messages('baz', limit=1)
282
self.assertEqual(1, len(response['messages']))
283
messages = response['messages']
284
response = client.get_messages(
285
'baz', limit=1, memo=response['next_memo'])
286
self.assertEqual(1, len(response['messages']))
287
messages.extend(response['messages'])
288
self.assertMessageIDs(['foo', 'bar'], messages)
290
def get_messages_member_order_test(self, key):
291
client = GrackleClient('localhost', 8439)
292
with ForkedFake.from_client(client,
293
{'baz': [{'message_id': 'foo', key: '2011-03-25'},
294
{'message_id': 'bar', key: '2011-03-24'}]}):
295
response = client.get_messages('baz')
296
self.assertIDOrder(['foo', 'bar'], response['messages'])
297
response = client.get_messages('baz', order=key)
298
self.assertIDOrder(['bar', 'foo'], response['messages'])
300
def test_get_messages_date_order(self):
301
self.get_messages_member_order_test('date')
303
def test_get_messages_author_order(self):
304
self.get_messages_member_order_test('author')
306
def test_get_messages_subject_order(self):
307
self.get_messages_member_order_test('subject')
309
def test_get_messages_thread_subject_order(self):
310
client = GrackleClient('localhost', 8439)
311
with ForkedFake.from_client(client, {'baz': [
312
{'message_id': 'bar', 'subject': 'y'},
313
{'message_id': 'qux', 'subject': 'z'},
314
{'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
316
response = client.get_messages('baz')
317
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
318
response = client.get_messages('baz', order='subject')
319
self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
320
response = client.get_messages('baz', order='thread_subject')
321
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
323
def test_get_messages_thread_oldest_order(self):
324
client = GrackleClient('localhost', 8439)
325
with ForkedFake.from_client(client, {'baz': [
326
{'message_id': 'bar', 'date': 'x'},
327
{'message_id': 'qux', 'date': 'z'},
328
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
330
response = client.get_messages('baz')
331
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
332
response = client.get_messages('baz', order='date')
333
self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
334
response = client.get_messages('baz', order='thread_oldest')
335
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
337
def test_get_messages_thread_newest_order(self):
338
client = GrackleClient('localhost', 8439)
339
with ForkedFake.from_client(client, {'baz': [
340
{'message_id': 'bar', 'date': 'x'},
341
{'message_id': 'qux', 'date': 'w'},
342
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
343
{'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
345
response = client.get_messages('baz', order='date')
347
['qux', 'bar', 'foo', 'baz'], response['messages'])
348
response = client.get_messages('baz', order='thread_newest')
350
['bar', 'foo', 'qux', 'baz'], response['messages'])
352
def test_get_messages_unsupported_order(self):
353
client = GrackleClient('localhost', 8439)
354
with ForkedFake.from_client(client,
355
{'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
356
{'message_id': 'bar', 'date': '2011-03-24'}]}):
357
with ExpectedException(UnsupportedOrder, ''):
358
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'])
80
with fake_grackle_service(client,
82
[{'message-id': 'foo'},
83
{'message-id': 'bar'}]}):
84
response = client.get_messages('baz')
85
self.assertEqual(['bar', 'foo'], sorted(response.keys()))