1
from BaseHTTPServer import (
3
BaseHTTPRequestHandler,
5
from email.message import Message
6
from email.mime.multipart import MIMEMultipart
7
from email.mime.text import MIMEText
11
from signal import SIGKILL
13
from StringIO import StringIO
15
from unittest import TestCase
16
from urlparse import urlparse
18
from testtools import ExpectedException
20
from grackle.client import (
23
UnsupportedDisplayType,
26
from grackle.store import (
32
def make_message(message_id, body='body', headers=None, hidden=False):
36
'Message-Id': message_id,
42
message_headers.update(headers.items())
44
message.set_payload(body)
45
for key, value in message_headers.items():
47
return make_json_message(message_id, message.as_string(), hidden)
50
def make_mime_message(message_id, body='body', headers=None, hidden=False,
51
attachment_type=None):
52
parts = MIMEMultipart()
53
parts.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
parts.attach(attachment)
60
return make_message(message_id, parts.as_string(), headers, hidden)
63
class ForkedFakeService:
64
"""A Grackle service fake, as a ContextManager."""
66
def __init__(self, port, message_archives=None, write_logs=False):
69
:param port: The tcp port to use.
70
:param message_archives: A dict of lists of dicts representing
71
archives of messages. The outer dict represents the archive,
72
the list represents the list of messages for that archive.
73
:param write_logs: If true, log messages will be written to stdout.
77
if message_archives is None:
78
self.message_archives = {}
80
self.message_archives = message_archives
81
self.read_end, self.write_end = os.pipe()
82
self.write_logs = write_logs
85
def from_client(client, message_archives=None):
86
"""Instantiate a ForkedFakeService from the client.
88
:param port: The client to provide service for.
89
:param message_archives: A dict of lists of dicts representing
90
archives of messages. The outer dict represents the archive,
91
the list represents the list of messages for that archive.
93
return ForkedFakeService(client.port, message_archives)
96
"""Tell the parent process that the server is ready for writes."""
97
os.write(self.write_end, 'asdf')
102
Fork and start a server in the child. Return when the server is ready
108
os.read(self.read_end, 1)
111
def start_server(self):
112
"""Start the HTTP server."""
113
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
114
service.store = MemoryStore(self.message_archives)
115
for archive_id, messages in service.store.message_archives.iteritems():
116
for message in messages:
117
message.setdefault('headers', {})
121
stream=sys.stderr, level=logging.INFO)
122
service.serve_forever()
124
def __exit__(self, exc_type, exc_val, traceback):
125
os.kill(self.pid, SIGKILL)
128
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
129
"""A request handler that forwards to server.store."""
131
def __init__(self, *args, **kwargs):
132
"""Constructor. Sets up logging."""
133
self.logger = logging.getLogger('http')
134
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
137
"""Create an archive or message on PUT."""
138
scheme, netloc, path, params, query_string, fragments = (
140
parts = path.split('/')
141
if parts[1] != 'archive':
142
# This is an unknonwn operation?
145
# This expected path is /archive/archive_id/message_id.
147
message = self.rfile.read(int(self.headers['content-length']))
148
self.server.store.put_message(parts[2], parts[3], message)
149
self.send_response(httplib.CREATED)
153
self.send_error(httplib.BAD_REQUEST)
156
"""Change a message on POST."""
157
scheme, netloc, path, params, query_string, fragments = (
159
parts = path.split('/')
160
if parts[1] != 'archive':
161
# This is an unknonwn operation?
164
# This expected path is /archive/archive_id/message_id.
166
# This expected path is /archive/archive_id/message_id.
167
response = self.server.store.hide_message(
168
parts[2], parts[3], query_string)
169
self.send_response(httplib.OK)
171
self.wfile.write(simplejson.dumps(response))
173
self.send_error(httplib.BAD_REQUEST)
176
"""Retrieve a list of messages on GET."""
177
scheme, netloc, path, params, query_string, fragments = (
179
parts = path.split('/')
180
if parts[1] == 'archive':
182
response = self.server.store.get_messages(
183
parts[2], query_string)
184
self.send_response(httplib.OK)
186
self.wfile.write(simplejson.dumps(response))
187
except Exception, error:
189
httplib.BAD_REQUEST, error.__doc__)
192
def log_message(self, format, *args):
193
"""Override log_message to use standard Python logging."""
194
message = "%s - - [%s] %s\n" % (
195
self.address_string(), self.log_date_time_string(), format % args)
196
self.logger.info(message)
199
class TestPutMessage(TestCase):
201
def test_put_message(self):
202
client = GrackleClient('localhost', 8420)
203
message_archives = {'arch1': []}
204
with ForkedFakeService.from_client(client, message_archives):
205
client.put_message('arch1', 'id1', StringIO('This is a message'))
206
response = client.get_messages('arch1')
207
self.assertEqual(1, len(response['messages']))
208
message = response['messages'][0]
209
self.assertEqual('id1', message['message_id'])
211
def test_put_message_without_archive(self):
212
client = GrackleClient('localhost', 8421)
213
message_archives = {'arch1': []}
214
with ForkedFakeService.from_client(client, message_archives):
215
with ExpectedException(Exception, 'wtf'):
216
client.put_message('no-archive', 'id1', StringIO('message'))
219
class TestGetMessages(TestCase):
221
def assertIDOrder(self, ids, messages):
222
self.assertEqual(ids, [m['message_id'] for m in messages])
224
def assertMessageIDs(self, ids, messages):
226
sorted(ids), sorted(messages, key=lambda m: m['message_id']))
228
def test_get_messages(self):
229
client = GrackleClient('localhost', 8430)
231
'baz': [make_message('foo'), make_message('bar')]}
232
with ForkedFakeService.from_client(client, archive):
233
response = client.get_messages('baz')
234
self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
235
response['messages']))
236
self.assertIs(None, response['next_memo'])
237
self.assertIs(None, response['previous_memo'])
239
def test_get_messages_by_id(self):
240
client = GrackleClient('localhost', 8437)
242
'baz': [make_message('foo'), make_message('bar')]}
243
with ForkedFakeService.from_client(client, archive):
244
response = client.get_messages('baz', message_ids=['foo'])
245
message, = response['messages']
246
self.assertEqual('foo', message['message_id'])
248
def test_get_messages_batching(self):
249
client = GrackleClient('localhost', 8438)
250
archive = {'baz': [make_message('foo'), make_message('bar')]}
251
with ForkedFakeService.from_client(client, archive):
252
response = client.get_messages('baz', limit=1)
253
self.assertEqual(1, len(response['messages']))
254
messages = response['messages']
255
response = client.get_messages(
256
'baz', limit=1, memo=response['next_memo'])
257
self.assertEqual(1, len(response['messages']))
258
messages.extend(response['messages'])
259
self.assertMessageIDs(['foo', 'bar'], messages)
261
def get_messages_member_order_test(self, key):
262
client = GrackleClient('localhost', 8439)
269
make_message('foo', headers={header_name: '2011-03-25'}),
270
make_message('bar', headers={header_name: '2011-03-24'}),
272
with ForkedFakeService.from_client(client, archive):
273
response = client.get_messages('baz')
274
self.assertIDOrder(['foo', 'bar'], response['messages'])
275
response = client.get_messages('baz', order=key)
276
self.assertIDOrder(['bar', 'foo'], response['messages'])
278
def test_get_messages_date_order(self):
279
self.get_messages_member_order_test('date')
281
def test_get_messages_author_order(self):
282
self.get_messages_member_order_test('author')
284
def test_get_messages_subject_order(self):
285
self.get_messages_member_order_test('subject')
287
def test_get_messages_thread_subject_order(self):
290
make_message('bar', headers={'subject': 'y'}),
291
make_message('qux', headers={'subject': 'z'}),
292
make_message('foo', headers={'subject': 'x',
293
'in-reply-to': 'qux'}),
295
client = GrackleClient('localhost', 8439)
296
with ForkedFakeService.from_client(client, archive):
297
response = client.get_messages('baz')
298
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
299
response = client.get_messages('baz', order='subject')
300
self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
301
response = client.get_messages('baz', order='thread_subject')
302
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
304
def test_get_messages_thread_oldest_order(self):
305
client = GrackleClient('localhost', 8439)
308
make_message('bar', headers={'date': 'x'}),
309
make_message('qux', headers={'date': 'z'}),
310
make_message('foo', headers={'date': 'y',
311
'in-reply-to': 'qux'}),
313
with ForkedFakeService.from_client(client, archive):
314
response = client.get_messages('baz')
315
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
316
response = client.get_messages('baz', order='date')
317
self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
318
response = client.get_messages('baz', order='thread_oldest')
319
self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
321
def test_get_messages_thread_newest_order(self):
322
client = GrackleClient('localhost', 8439)
325
make_message('bar', headers={'date': 'x'}),
326
make_message('qux', headers={'date': 'w'}),
327
make_message('foo', headers={'date': 'y',
328
'in-reply-to': 'bar'}),
329
make_message('baz', headers={'date': 'z',
330
'in-reply-to': 'qux'}),
332
with ForkedFakeService.from_client(client, archive):
333
response = client.get_messages('baz', order='date')
335
['qux', 'bar', 'foo', 'baz'], response['messages'])
336
response = client.get_messages('baz', order='thread_newest')
338
['bar', 'foo', 'qux', 'baz'], response['messages'])
340
def test_get_messages_unsupported_order(self):
341
client = GrackleClient('localhost', 8439)
344
make_message('foo', headers={'date': '2011-03-25'}),
345
make_message('foo', headers={'date': '2011-03-24'}),
347
with ForkedFakeService.from_client(client, archive):
348
with ExpectedException(UnsupportedOrder, ''):
349
client.get_messages('baz', order='nonsense')
351
def test_get_messages_headers_no_headers(self):
352
client = GrackleClient('localhost', 8440)
353
archive = {'baz': [make_message('foo')]}
354
with ForkedFakeService.from_client(client, archive):
355
response = client.get_messages('baz', headers=[
356
'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
357
first_message = response['messages'][0]
358
self.assertEqual('foo', first_message['message_id'])
359
self.assertEqual({}, first_message['headers'])
361
def test_get_messages_headers_exclude_headers(self):
362
client = GrackleClient('localhost', 8441)
364
'baz': [make_message('foo', headers={'From': 'me'})]}
365
with ForkedFakeService.from_client(client, archive):
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_include_headers(self):
373
client = GrackleClient('localhost', 8442)
376
make_message('foo', headers={'From': 'me', 'To': 'you'})]}
377
with ForkedFakeService.from_client(client, archive):
378
response = client.get_messages('baz', headers=[
380
first_message = response['messages'][0]
381
self.assertEqual('foo', first_message['message_id'])
382
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
384
def test_get_messages_max_body_length(self):
385
client = GrackleClient('localhost', 8443)
386
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
387
with ForkedFakeService.from_client(client, archive):
388
response = client.get_messages('baz', max_body_length=3)
389
first_message = response['messages'][0]
390
self.assertEqual('abc', first_message['body'])
392
def test_include_hidden(self):
393
client = GrackleClient('localhost', 8444)
396
make_message('foo', hidden=True),
397
make_message('bar', hidden=False),
399
with ForkedFakeService.from_client(client, archive):
400
response = client.get_messages('baz', include_hidden=True)
401
self.assertMessageIDs(['bar', 'foo'], response['messages'])
402
response = client.get_messages('baz', include_hidden=False)
403
self.assertMessageIDs(['bar'], response['messages'])
405
def test_display_type_unknown_value(self):
406
client = GrackleClient('localhost', 8445)
407
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
408
with ForkedFakeService.from_client(client, archive):
409
with ExpectedException(UnsupportedDisplayType, ''):
410
client.get_messages('baz', display_type='unknown')
412
def test_display_type_headers_only(self):
413
client = GrackleClient('localhost', 8446)
416
make_message('foo', body=u'abcdefghi',
417
headers={'From': 'me', 'To': 'you'})]}
418
with ForkedFakeService.from_client(client, archive):
419
response = client.get_messages('baz', display_type='headers-only')
420
first_message = response['messages'][0]
421
self.assertEqual('foo', first_message['message_id'])
423
archive['baz'][0]['headers'], first_message['headers'])
424
self.assertNotIn('body', first_message)
426
def test_display_type_text_only(self):
427
client = GrackleClient('localhost', 8446)
432
headers={'From': 'me', 'To': 'you'},
433
attachment_type='text/x-diff')]}
434
with ForkedFakeService.from_client(client, archive):
435
response = client.get_messages('baz', display_type='text-only')
436
first_message = response['messages'][0]
437
self.assertEqual('foo', first_message['message_id'])
438
self.assertEqual('me', first_message['headers']['From'])
439
self.assertEqual('you', first_message['headers']['To'])
440
self.assertEqual(archive['baz'][0]['body'], first_message['body'])
442
def test_display_type_all(self):
443
client = GrackleClient('localhost', 8447)
448
headers={'From': 'me', 'To': 'you'},
449
attachment_type='text/x-diff')]}
450
with ForkedFakeService.from_client(client, archive):
451
response = client.get_messages('baz', display_type='all')
452
first_message = response['messages'][0]
453
self.assertEqual('foo', first_message['message_id'])
454
self.assertEqual('me', first_message['headers']['From'])
455
self.assertEqual('you', first_message['headers']['To'])
456
self.assertEqual(archive['baz'][0]['body'], first_message['body'])
458
def test_date_range(self):
459
client = GrackleClient('localhost', 8448)
463
'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
465
'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
467
'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
469
'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
471
'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
473
with ForkedFakeService.from_client(client, archive):
474
response = client.get_messages(
475
'baz', date_range='2012-01-01..2012-01-31')
476
ids = sorted(m['message_id'] for m in response['messages'])
477
self.assertEqual(['bar', 'naf', 'qux'], ids)
479
def test_date_range_unparsabledaterange(self):
480
client = GrackleClient('localhost', 8449)
481
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
482
with ForkedFakeService.from_client(client, archive):
483
with ExpectedException(UnparsableDateRange, ''):
484
client.get_messages('baz', date_range='2012-01-01')
486
def test_date_range_unparsabledaterange_missing_part(self):
487
client = GrackleClient('localhost', 8450)
488
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
489
with ForkedFakeService.from_client(client, archive):
490
with ExpectedException(UnparsableDateRange, ''):
491
client.get_messages('baz', date_range='2012-01-01..')
493
def test_date_range_unparsabledaterange_extra_part(self):
494
client = GrackleClient('localhost', 8451)
495
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
496
with ForkedFakeService.from_client(client, archive):
497
with ExpectedException(UnparsableDateRange, ''):
498
client.get_messages('baz', date_range='2012-01..12-02..12-03')
501
class TestHideMessages(TestCase):
503
def test_hide_message_true(self):
504
client = GrackleClient('localhost', 8470)
507
make_message('foo', hidden=False),
509
with ForkedFakeService.from_client(client, archive):
510
response = client.hide_message('baz', 'foo', hidden=True)
511
self.assertEqual('foo', response['message_id'])
512
self.assertIs(True, response['hidden'])
514
def test_hide_message_false(self):
515
client = GrackleClient('localhost', 8470)
518
make_message('foo', hidden=True),
520
with ForkedFakeService.from_client(client, archive):
521
response = client.hide_message('baz', 'foo', hidden=False)
522
self.assertEqual('foo', response['message_id'])
523
self.assertIs(False, response['hidden'])