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
1
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 (
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.
80
self.messages = messages
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)
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.messages)
115
for archive_id, messages in service.store.messages.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 a message on POST."""
138
message = self.rfile.read(int(self.headers['content-length']))
139
if message == 'This is a message':
140
self.send_response(httplib.CREATED)
144
self.send_error(httplib.BAD_REQUEST)
147
"""Retrieve a list of messages on GET."""
148
scheme, netloc, path, params, query_string, fragments = (
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)
3
170
class TestPutMessage(TestCase):
5
172
def test_put_message(self):
173
client = GrackleClient('localhost', 8420)
174
with ForkedFakeService.from_client(client):
175
client.put_message('arch1', 'asdf', StringIO('This is a message'))
176
with ExpectedException(Exception, 'wtf'):
177
client.put_message('arch1', 'asdf',
178
StringIO('This is not a message'))
181
class TestGetMessages(TestCase):
183
def assertIDOrder(self, ids, messages):
184
self.assertEqual(ids, [m['message_id'] for m in messages])
186
def assertMessageIDs(self, ids, messages):
188
sorted(ids), sorted(messages, key=lambda m: m['message_id']))
190
def test_get_messages(self):
191
client = GrackleClient('localhost', 8430)
193
'baz': [make_message('foo'), make_message('bar')]}
194
with ForkedFakeService.from_client(client, archive):
195
response = client.get_messages('baz')
196
self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
197
response['messages']))
198
self.assertIs(None, response['next_memo'])
199
self.assertIs(None, response['previous_memo'])
201
def test_get_messages_by_id(self):
202
client = GrackleClient('localhost', 8437)
204
'baz': [make_message('foo'), make_message('bar')]}
205
with ForkedFakeService.from_client(client, archive):
206
response = client.get_messages('baz', message_ids=['foo'])
207
message, = response['messages']
208
self.assertEqual('foo', message['message_id'])
210
def test_get_messages_batching(self):
211
client = GrackleClient('localhost', 8438)
212
archive = {'baz': [make_message('foo'), make_message('bar')]}
213
with ForkedFakeService.from_client(client, archive):
214
response = client.get_messages('baz', limit=1)
215
self.assertEqual(1, len(response['messages']))
216
messages = response['messages']
217
response = client.get_messages(
218
'baz', limit=1, memo=response['next_memo'])
219
self.assertEqual(1, len(response['messages']))
220
messages.extend(response['messages'])
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')