3
3
BaseHTTPRequestHandler,
5
from email.message import Message
6
from email.mime.multipart import MIMEMultipart
7
from email.mime.text import MIMEText
7
11
from signal import SIGKILL
9
13
from StringIO import StringIO
10
15
from unittest import TestCase
11
16
from urlparse import urlparse
12
from urlparse import parse_qs
14
18
from testtools import ExpectedException
16
20
from grackle.client import (
23
def __init__(self, port, messages=None):
23
UnsupportedDisplayType,
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:
64
"""A Grackle service fake, as a ContextManager."""
66
def __init__(self, port, messages=None, write_logs=False):
69
:param port: The tcp port to use.
70
:param messages: A dict of lists of dicts representing messages. The
71
outer dict represents the archive, the list represents the list of
72
messages for that archive.
73
:param write_logs: If true, log messages will be written to stdout.
26
self.messages = messages
80
self.messages = messages
27
81
self.read_end, self.write_end = os.pipe()
82
self.write_logs = write_logs
85
def from_client(client, messages=None):
86
"""Instantiate a ForkedFakeService from the client.
88
:param port: The client to provide service for.
89
:param messages: A dict of lists of dicts representing messages. The
90
outer dict represents the archive, the list represents the list of
91
messages for that archive.
93
return ForkedFakeService(client.port, messages)
29
95
def is_ready(self):
96
"""Tell the parent process that the server is ready for writes."""
30
97
os.write(self.write_end, 'asdf')
32
99
def __enter__(self):
102
Fork and start a server in the child. Return when the server is ready
35
106
self.start_server()
59
144
self.send_error(httplib.BAD_REQUEST)
147
"""Retrieve a list of messages on GET."""
62
148
scheme, netloc, path, params, query_string, fragments = (
63
149
urlparse(self.path))
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']]
72
limit = parameters.get('limit', 100)
73
memo = parameters.get('memo')
74
message_id_indices = dict(
75
(m['message_id'], idx) for idx, m in enumerate(messages))
79
start = message_id_indices[memo.encode('rot13')]
81
previous_memo = messages[start - 1]['message_id'].encode('rot13')
84
end = min(start + limit, len(messages))
85
if end < len(messages):
86
next_memo = messages[end]['message_id'].encode('rot13')
89
messages = messages[start:end]
92
'next_memo': next_memo,
93
'previous_memo': previous_memo
95
self.wfile.write(simplejson.dumps(response))
98
def fake_grackle_service(client, messages=None):
101
return ForkedFake(client.port, messages)
150
parts = path.split('/')
151
if parts[1] == 'archive':
153
response = self.server.store.get_messages(
154
parts[2], query_string)
155
self.send_response(httplib.OK)
157
self.wfile.write(simplejson.dumps(response))
158
except Exception, error:
160
httplib.BAD_REQUEST, error.__doc__)
163
def log_message(self, format, *args):
164
"""Override log_message to use standard Python logging."""
165
message = "%s - - [%s] %s\n" % (
166
self.address_string(), self.log_date_time_string(), format % args)
167
self.logger.info(message)
104
170
class TestPutMessage(TestCase):
106
172
def test_put_message(self):
107
client = GrackleClient('localhost', 8436)
108
with fake_grackle_service(client):
173
client = GrackleClient('localhost', 8420)
174
with ForkedFakeService.from_client(client):
109
175
client.put_message('arch1', 'asdf', StringIO('This is a message'))
110
176
with ExpectedException(Exception, 'wtf'):
111
177
client.put_message('arch1', 'asdf',
154
219
self.assertEqual(1, len(response['messages']))
155
220
messages.extend(response['messages'])
156
221
self.assertMessageIDs(['foo', 'bar'], messages)
223
def get_messages_member_order_test(self, key):
224
client = GrackleClient('localhost', 8439)
227
make_message('foo', headers={key: '2011-03-25'}),
228
make_message('bar', headers={key: '2011-03-24'}),
230
with ForkedFakeService.from_client(client, archive):
231
response = client.get_messages('baz')
232
self.assertIDOrder(['foo', 'bar'], response['messages'])
233
response = client.get_messages('baz', order=key)
234
self.assertIDOrder(['bar', 'foo'], response['messages'])
236
def test_get_messages_date_order(self):
237
self.get_messages_member_order_test('date')
239
def test_get_messages_author_order(self):
240
self.get_messages_member_order_test('author')
242
def test_get_messages_subject_order(self):
243
self.get_messages_member_order_test('subject')
245
def test_get_messages_thread_subject_order(self):
248
make_message('bar', headers={'subject': 'y'}),
249
make_message('qux', headers={'subject': 'z'}),
250
make_message('foo', headers={'subject': 'x',
251
'in-reply-to': 'qux'}),
253
client = GrackleClient('localhost', 8439)
254
with ForkedFakeService.from_client(client, archive):
255
response = client.get_messages('baz')
256
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
257
response = client.get_messages('baz', order='subject')
258
self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
259
response = client.get_messages('baz', order='thread_subject')
260
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
262
def test_get_messages_thread_oldest_order(self):
263
client = GrackleClient('localhost', 8439)
266
make_message('bar', headers={'date': 'x'}),
267
make_message('qux', headers={'date': 'z'}),
268
make_message('foo', headers={'date': 'y',
269
'in-reply-to': 'qux'}),
271
with ForkedFakeService.from_client(client, archive):
272
response = client.get_messages('baz')
273
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
274
response = client.get_messages('baz', order='date')
275
self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
276
response = client.get_messages('baz', order='thread_oldest')
277
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
279
def test_get_messages_thread_newest_order(self):
280
client = GrackleClient('localhost', 8439)
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):
291
response = client.get_messages('baz', order='date')
293
['qux', 'bar', 'foo', 'baz'], response['messages'])
294
response = client.get_messages('baz', order='thread_newest')
296
['bar', 'foo', 'qux', 'baz'], response['messages'])
298
def test_get_messages_unsupported_order(self):
299
client = GrackleClient('localhost', 8439)
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):
306
with ExpectedException(UnsupportedOrder, ''):
307
client.get_messages('baz', order='nonsense')
309
def test_get_messages_headers_no_headers(self):
310
client = GrackleClient('localhost', 8440)
311
archive = {'baz': [make_message('foo')]}
312
with ForkedFakeService.from_client(client, archive):
313
response = client.get_messages('baz', headers=[
314
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
315
first_message = response['messages'][0]
316
self.assertEqual('foo', first_message['message_id'])
317
self.assertEqual({}, first_message['headers'])
319
def test_get_messages_headers_exclude_headers(self):
320
client = GrackleClient('localhost', 8441)
322
'baz': [make_message('foo', headers={'From': 'me'})]}
323
with ForkedFakeService.from_client(client, archive):
324
response = client.get_messages('baz', headers=[
325
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
326
first_message = response['messages'][0]
327
self.assertEqual('foo', first_message['message_id'])
328
self.assertEqual({}, first_message['headers'])
330
def test_get_messages_headers_include_headers(self):
331
client = GrackleClient('localhost', 8442)
334
make_message('foo', headers={'From': 'me', 'To': 'you'})]}
335
with ForkedFakeService.from_client(client, archive):
336
response = client.get_messages('baz', headers=[
338
first_message = response['messages'][0]
339
self.assertEqual('foo', first_message['message_id'])
340
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
342
def test_get_messages_max_body_length(self):
343
client = GrackleClient('localhost', 8443)
344
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
345
with ForkedFakeService.from_client(client, archive):
346
response = client.get_messages('baz', max_body_length=3)
347
first_message = response['messages'][0]
348
self.assertEqual('abc', first_message['body'])
350
def test_include_hidden(self):
351
client = GrackleClient('localhost', 8444)
354
make_message('foo', hidden=True),
355
make_message('bar', hidden=False),
357
with ForkedFakeService.from_client(client, archive):
358
response = client.get_messages('baz', include_hidden=True)
359
self.assertMessageIDs(['bar', 'foo'], response['messages'])
360
response = client.get_messages('baz', include_hidden=False)
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')