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]
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:
75
if (not parameters['include_hidden']
76
and message.get('hidden', False)):
79
if ('message_ids' in parameters
80
and message['message_id'] not in parameters['message_ids']):
82
message = dict(message)
83
if 'headers' in parameters:
85
(k, v) for k, v in message['headers'].iteritems()
86
if k in parameters['headers'])
87
message['headers'] = headers
88
max_body = parameters.get('max_body_length')
89
if max_body is not None:
90
message['body'] = message['body'][:max_body]
91
new_messages.append(message)
92
messages = new_messages
93
limit = parameters.get('limit', 100)
94
memo = parameters.get('memo')
95
message_id_indices = dict(
96
(m['message_id'], idx) for idx, m in enumerate(messages))
100
start = message_id_indices[memo.encode('rot13')]
102
previous_memo = messages[start - 1]['message_id'].encode('rot13')
105
end = min(start + limit, len(messages))
106
if end < len(messages):
107
next_memo = messages[end]['message_id'].encode('rot13')
110
messages = messages[start:end]
113
'messages': messages,
114
'next_memo': next_memo,
115
'previous_memo': previous_memo
121
"""A Grackle service fake, as a ContextManager."""
123
def __init__(self, port, messages=None, write_logs=False):
125
:param port: The tcp port to use
126
:param messages: A dict of lists of dicts representing messages. The
127
outer dict represents the archive, the list represents the list of
128
messages for that archive.
129
:param write_logs: If true, log messages will be written to stdout.
20
def __init__(self, port, messages=None):
136
self.messages = messages
23
self.messages = messages
137
24
self.read_end, self.write_end = os.pipe()
138
self.write_logs = write_logs
141
def from_client(client, messages=None):
142
"""Instantiate a ForkedFake from the client.
144
:param port: The client to provide service for.
145
:param messages: A dict of lists of dicts representing messages. The
146
outer dict represents the archive, the list represents the list of
147
messages for that archive.
149
return ForkedFake(client.port, messages)
151
26
def is_ready(self):
152
"""Tell the parent process that the server is ready for writes."""
153
27
os.write(self.write_end, 'asdf')
155
29
def __enter__(self):
158
Fork and start a server in the child. Return when the server is ready
162
32
self.start_server()
242
76
class TestGetMessages(TestCase):
244
def assertIDOrder(self, ids, messages):
245
self.assertEqual(ids, [m['message_id'] for m in messages])
247
def assertMessageIDs(self, ids, messages):
249
sorted(ids), sorted(messages, key=lambda m: m['message_id']))
251
78
def test_get_messages(self):
252
79
client = GrackleClient('localhost', 8435)
253
with ForkedFake.from_client(client,
255
[{'message_id': 'foo'},
256
{'message_id': 'bar'}]}):
257
response = client.get_messages('baz')
258
self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
259
response['messages']))
260
self.assertIs(None, response['next_memo'])
261
self.assertIs(None, response['previous_memo'])
263
def test_get_messages_by_id(self):
264
client = GrackleClient('localhost', 8437)
265
with ForkedFake.from_client(client,
267
[{'message_id': 'foo'},
268
{'message_id': 'bar'}]}):
269
response = client.get_messages('baz', message_ids=['foo'])
270
message, = response['messages']
271
self.assertEqual('foo', message['message_id'])
273
def test_get_messages_batching(self):
274
client = GrackleClient('localhost', 8438)
275
with ForkedFake.from_client(client,
277
[{'message_id': 'foo'},
278
{'message_id': 'bar'}]}):
279
response = client.get_messages('baz', limit=1)
280
self.assertEqual(1, len(response['messages']))
281
messages = response['messages']
282
response = client.get_messages(
283
'baz', limit=1, memo=response['next_memo'])
284
self.assertEqual(1, len(response['messages']))
285
messages.extend(response['messages'])
286
self.assertMessageIDs(['foo', 'bar'], messages)
288
def get_messages_member_order_test(self, key):
289
client = GrackleClient('localhost', 8439)
290
with ForkedFake.from_client(client,
291
{'baz': [{'message_id': 'foo', key: '2011-03-25'},
292
{'message_id': 'bar', key: '2011-03-24'}]}):
293
response = client.get_messages('baz')
294
self.assertIDOrder(['foo', 'bar'], response['messages'])
295
response = client.get_messages('baz', order=key)
296
self.assertIDOrder(['bar', 'foo'], response['messages'])
298
def test_get_messages_date_order(self):
299
self.get_messages_member_order_test('date')
301
def test_get_messages_author_order(self):
302
self.get_messages_member_order_test('author')
304
def test_get_messages_subject_order(self):
305
self.get_messages_member_order_test('subject')
307
def test_get_messages_thread_subject_order(self):
308
client = GrackleClient('localhost', 8439)
309
with ForkedFake.from_client(client, {'baz': [
310
{'message_id': 'bar', 'subject': 'y'},
311
{'message_id': 'qux', 'subject': 'z'},
312
{'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
314
response = client.get_messages('baz')
315
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
316
response = client.get_messages('baz', order='subject')
317
self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
318
response = client.get_messages('baz', order='thread_subject')
319
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
321
def test_get_messages_thread_oldest_order(self):
322
client = GrackleClient('localhost', 8439)
323
with ForkedFake.from_client(client, {'baz': [
324
{'message_id': 'bar', 'date': 'x'},
325
{'message_id': 'qux', 'date': 'z'},
326
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
328
response = client.get_messages('baz')
329
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
330
response = client.get_messages('baz', order='date')
331
self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
332
response = client.get_messages('baz', order='thread_oldest')
333
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
335
def test_get_messages_thread_newest_order(self):
336
client = GrackleClient('localhost', 8439)
337
with ForkedFake.from_client(client, {'baz': [
338
{'message_id': 'bar', 'date': 'x'},
339
{'message_id': 'qux', 'date': 'w'},
340
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
341
{'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
343
response = client.get_messages('baz', order='date')
345
['qux', 'bar', 'foo', 'baz'], response['messages'])
346
response = client.get_messages('baz', order='thread_newest')
348
['bar', 'foo', 'qux', 'baz'], response['messages'])
350
def test_get_messages_unsupported_order(self):
351
client = GrackleClient('localhost', 8439)
352
with ForkedFake.from_client(client,
353
{'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
354
{'message_id': 'bar', 'date': '2011-03-24'}]}):
355
with ExpectedException(UnsupportedOrder, ''):
356
client.get_messages('baz', order='nonsense')
358
def test_get_messages_headers_no_headers(self):
359
client = GrackleClient('localhost', 8440)
360
with ForkedFake.from_client(client,
362
{'message_id': 'foo'}
364
response = client.get_messages('baz', headers=[
365
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
366
first_message = response['messages'][0]
367
self.assertEqual('foo', first_message['message_id'])
368
self.assertEqual({}, first_message['headers'])
370
def test_get_messages_headers_exclude_headers(self):
371
client = GrackleClient('localhost', 8441)
372
with ForkedFake.from_client(client,
374
{'message_id': 'foo', 'headers': {'From': 'me'}}
376
response = client.get_messages('baz', headers=[
377
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
378
first_message = response['messages'][0]
379
self.assertEqual('foo', first_message['message_id'])
380
self.assertEqual({}, first_message['headers'])
382
def test_get_messages_headers_include_headers(self):
383
client = GrackleClient('localhost', 8442)
384
with ForkedFake.from_client(client,
386
{'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
388
response = client.get_messages('baz', headers=[
390
first_message = response['messages'][0]
391
self.assertEqual('foo', first_message['message_id'])
392
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
394
def test_get_messages_max_body_length(self):
395
client = GrackleClient('localhost', 8443)
396
with ForkedFake.from_client(client,
398
{'message_id': 'foo', 'body': u'abcdefghi'}
400
response = client.get_messages('baz', max_body_length=3)
401
first_message = response['messages'][0]
402
self.assertEqual('abc', first_message['body'])
404
def test_include_hidden(self):
405
client = GrackleClient('localhost', 8444)
406
with ForkedFake.from_client(client,
408
{'message_id': 'foo', 'hidden': True},
409
{'message_id': 'bar', 'hidden': False}
411
response = client.get_messages('baz', include_hidden=True)
412
self.assertMessageIDs(['bar', 'foo'], response['messages'])
413
response = client.get_messages('baz', include_hidden=False)
414
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()))