12
15
from unittest import TestCase
13
16
from urlparse import urlparse
14
from urlparse import parse_qs
16
18
from testtools import ExpectedException
18
20
from grackle.client import (
23
UnsupportedDisplayType,
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
26
from grackle.store import (
31
def make_message(message_id, body='body', headers=None, hidden=False):
34
headers['Message-Id'] = message_id
36
'message_id': message_id,
38
'thread_id': message_id,
39
'date': headers.get('date', '2005-01-01'),
40
'subject': headers.get('subject', 'subject'),
41
'author': headers.get('author', 'author'),
44
'replies': headers.get('in-reply-to', None),
50
def make_mime_message(message_id, body='body', headers=None, hidden=False,
51
attachment_type=None):
52
message = MIMEMultipart()
53
message.attach(MIMEText(body))
54
if attachment_type is not None:
55
attachment = Message()
56
attachment.set_payload('attactment data.')
57
attachment['Content-Type'] = attachment_type
58
attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
59
message.attach(attachment)
60
return make_message(message_id, message.get_payload(), headers, hidden)
63
class ForkedFakeService:
121
64
"""A Grackle service fake, as a ContextManager."""
123
66
def __init__(self, port, messages=None, write_logs=False):
125
:param port: The tcp port to use
69
:param port: The tcp port to use.
126
70
:param messages: A dict of lists of dicts representing messages. The
127
71
outer dict represents the archive, the list represents the list of
128
72
messages for that archive.
263
201
def test_get_messages_by_id(self):
264
202
client = GrackleClient('localhost', 8437)
265
with ForkedFake.from_client(client,
267
[{'message_id': 'foo'},
268
{'message_id': 'bar'}]}):
204
'baz': [make_message('foo'), make_message('bar')]}
205
with ForkedFakeService.from_client(client, archive):
269
206
response = client.get_messages('baz', message_ids=['foo'])
270
207
message, = response['messages']
271
208
self.assertEqual('foo', message['message_id'])
273
210
def test_get_messages_batching(self):
274
211
client = GrackleClient('localhost', 8438)
275
with ForkedFake.from_client(client,
277
[{'message_id': 'foo'},
278
{'message_id': 'bar'}]}):
212
archive = {'baz': [make_message('foo'), make_message('bar')]}
213
with ForkedFakeService.from_client(client, archive):
279
214
response = client.get_messages('baz', limit=1)
280
215
self.assertEqual(1, len(response['messages']))
281
216
messages = response['messages']
335
279
def test_get_messages_thread_newest_order(self):
336
280
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'},
283
make_message('bar', headers={'date': 'x'}),
284
make_message('qux', headers={'date': 'w'}),
285
make_message('foo', headers={'date': 'y',
286
'in-reply-to': 'bar'}),
287
make_message('baz', headers={'date': 'z',
288
'in-reply-to': 'qux'}),
290
with ForkedFakeService.from_client(client, archive):
343
291
response = client.get_messages('baz', order='date')
344
292
self.assertIDOrder(
345
293
['qux', 'bar', 'foo', 'baz'], response['messages'])
350
298
def test_get_messages_unsupported_order(self):
351
299
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'}]}):
302
make_message('foo', headers={'date': '2011-03-25'}),
303
make_message('foo', headers={'date': '2011-03-24'}),
305
with ForkedFakeService.from_client(client, archive):
355
306
with ExpectedException(UnsupportedOrder, ''):
356
307
client.get_messages('baz', order='nonsense')
358
309
def test_get_messages_headers_no_headers(self):
359
310
client = GrackleClient('localhost', 8440)
360
with ForkedFake.from_client(client,
362
{'message_id': 'foo'}
311
archive = {'baz': [make_message('foo')]}
312
with ForkedFakeService.from_client(client, archive):
364
313
response = client.get_messages('baz', headers=[
365
314
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
366
315
first_message = response['messages'][0]
394
342
def test_get_messages_max_body_length(self):
395
343
client = GrackleClient('localhost', 8443)
396
with ForkedFake.from_client(client,
398
{'message_id': 'foo', 'body': u'abcdefghi'}
344
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
345
with ForkedFakeService.from_client(client, archive):
400
346
response = client.get_messages('baz', max_body_length=3)
401
347
first_message = response['messages'][0]
402
348
self.assertEqual('abc', first_message['body'])
404
350
def test_include_hidden(self):
405
351
client = GrackleClient('localhost', 8444)
406
with ForkedFake.from_client(client,
408
{'message_id': 'foo', 'hidden': True},
409
{'message_id': 'bar', 'hidden': False}
354
make_message('foo', hidden=True),
355
make_message('bar', hidden=False),
357
with ForkedFakeService.from_client(client, archive):
411
358
response = client.get_messages('baz', include_hidden=True)
412
359
self.assertMessageIDs(['bar', 'foo'], response['messages'])
413
360
response = client.get_messages('baz', include_hidden=False)
414
361
self.assertMessageIDs(['bar'], response['messages'])
363
def test_display_type_unknown_value(self):
364
client = GrackleClient('localhost', 8445)
365
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
366
with ForkedFakeService.from_client(client, archive):
367
with ExpectedException(UnsupportedDisplayType, ''):
368
client.get_messages('baz', display_type='unknown')
370
def test_display_type_headers_only(self):
371
client = GrackleClient('localhost', 8446)
374
make_message('foo', body=u'abcdefghi',
375
headers={'From': 'me', 'To': 'you'})]}
376
with ForkedFakeService.from_client(client, archive):
377
response = client.get_messages('baz', display_type='headers-only')
378
first_message = response['messages'][0]
379
self.assertEqual('foo', first_message['message_id'])
381
{'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
382
first_message['headers'])
383
self.assertNotIn('body', first_message)
385
def test_display_type_text_only(self):
386
client = GrackleClient('localhost', 8446)
391
headers={'From': 'me', 'To': 'you'},
392
attachment_type='text/x-diff')]}
393
with ForkedFakeService.from_client(client, archive):
394
response = client.get_messages('baz', display_type='text-only')
395
first_message = response['messages'][0]
396
self.assertEqual('foo', first_message['message_id'])
397
self.assertEqual('me', first_message['headers']['From'])
398
self.assertEqual('you', first_message['headers']['To'])
399
self.assertEqual('abcdefghi', first_message['body'])
401
def test_display_type_all(self):
402
client = GrackleClient('localhost', 8447)
407
headers={'From': 'me', 'To': 'you'},
408
attachment_type='text/x-diff')]}
409
with ForkedFakeService.from_client(client, archive):
410
response = client.get_messages('baz', display_type='all')
411
first_message = response['messages'][0]
412
self.assertEqual('foo', first_message['message_id'])
413
self.assertEqual('me', first_message['headers']['From'])
414
self.assertEqual('you', first_message['headers']['To'])
416
'abcdefghi\n\nattactment data.', first_message['body'])
418
def test_date_range(self):
419
client = GrackleClient('localhost', 8448)
423
'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
425
'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
427
'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
429
'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
431
'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
433
with ForkedFakeService.from_client(client, archive):
434
response = client.get_messages(
435
'baz', date_range='2012-01-01..2012-01-31')
436
ids = sorted(m['message_id'] for m in response['messages'])
437
self.assertEqual(['bar', 'naf', 'qux'], ids)
439
def test_date_range_unparsabledaterange(self):
440
client = GrackleClient('localhost', 8449)
441
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
442
with ForkedFakeService.from_client(client, archive):
443
with ExpectedException(UnparsableDateRange, ''):
444
client.get_messages('baz', date_range='2012-01-01')
446
def test_date_range_unparsabledaterange_missing_part(self):
447
client = GrackleClient('localhost', 8450)
448
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
449
with ForkedFakeService.from_client(client, archive):
450
with ExpectedException(UnparsableDateRange, ''):
451
client.get_messages('baz', date_range='2012-01-01..')
453
def test_date_range_unparsabledaterange_extra_part(self):
454
client = GrackleClient('localhost', 8451)
455
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
456
with ForkedFakeService.from_client(client, archive):
457
with ExpectedException(UnparsableDateRange, ''):
458
client.get_messages('baz', date_range='2012-01..12-02..12-03')