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)
120
63
class ForkedFakeService:
264
201
def test_get_messages_by_id(self):
265
202
client = GrackleClient('localhost', 8437)
266
with ForkedFakeService.from_client(client,
268
[{'message_id': 'foo'},
269
{'message_id': 'bar'}]}):
204
'baz': [make_message('foo'), make_message('bar')]}
205
with ForkedFakeService.from_client(client, archive):
270
206
response = client.get_messages('baz', message_ids=['foo'])
271
207
message, = response['messages']
272
208
self.assertEqual('foo', message['message_id'])
274
210
def test_get_messages_batching(self):
275
211
client = GrackleClient('localhost', 8438)
276
with ForkedFakeService.from_client(client,
278
[{'message_id': 'foo'},
279
{'message_id': 'bar'}]}):
212
archive = {'baz': [make_message('foo'), make_message('bar')]}
213
with ForkedFakeService.from_client(client, archive):
280
214
response = client.get_messages('baz', limit=1)
281
215
self.assertEqual(1, len(response['messages']))
282
216
messages = response['messages']
336
279
def test_get_messages_thread_newest_order(self):
337
280
client = GrackleClient('localhost', 8439)
338
with ForkedFakeService.from_client(client, {'baz': [
339
{'message_id': 'bar', 'date': 'x'},
340
{'message_id': 'qux', 'date': 'w'},
341
{'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
342
{'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):
344
291
response = client.get_messages('baz', order='date')
345
292
self.assertIDOrder(
346
293
['qux', 'bar', 'foo', 'baz'], response['messages'])
351
298
def test_get_messages_unsupported_order(self):
352
299
client = GrackleClient('localhost', 8439)
353
with ForkedFakeService.from_client(client,
354
{'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
355
{'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):
356
306
with ExpectedException(UnsupportedOrder, ''):
357
307
client.get_messages('baz', order='nonsense')
359
309
def test_get_messages_headers_no_headers(self):
360
310
client = GrackleClient('localhost', 8440)
361
with ForkedFakeService.from_client(client,
363
{'message_id': 'foo'}
311
archive = {'baz': [make_message('foo')]}
312
with ForkedFakeService.from_client(client, archive):
365
313
response = client.get_messages('baz', headers=[
366
314
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
367
315
first_message = response['messages'][0]
395
342
def test_get_messages_max_body_length(self):
396
343
client = GrackleClient('localhost', 8443)
397
with ForkedFakeService.from_client(client,
399
{'message_id': 'foo', 'body': u'abcdefghi'}
344
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
345
with ForkedFakeService.from_client(client, archive):
401
346
response = client.get_messages('baz', max_body_length=3)
402
347
first_message = response['messages'][0]
403
348
self.assertEqual('abc', first_message['body'])
405
350
def test_include_hidden(self):
406
351
client = GrackleClient('localhost', 8444)
407
with ForkedFakeService.from_client(client,
409
{'message_id': 'foo', 'hidden': True},
410
{'message_id': 'bar', 'hidden': False}
354
make_message('foo', hidden=True),
355
make_message('bar', hidden=False),
357
with ForkedFakeService.from_client(client, archive):
412
358
response = client.get_messages('baz', include_hidden=True)
413
359
self.assertMessageIDs(['bar', 'foo'], response['messages'])
414
360
response = client.get_messages('baz', include_hidden=False)
415
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')