~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-03-16 16:12:13 UTC
  • Revision ID: curtis.hovey@canonical.com-20120316161213-htrw58db1ojtl8d9
Always use an rfc822 message in tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
    HTTPServer,
3
3
    BaseHTTPRequestHandler,
4
4
    )
5
 
import contextlib
 
5
from email.message import Message
 
6
from email.mime.multipart import MIMEMultipart
 
7
from email.mime.text import MIMEText
 
8
import httplib
 
9
import logging
6
10
import os
 
11
from signal import SIGKILL
 
12
import simplejson
7
13
from StringIO import StringIO
 
14
import sys
8
15
from unittest import TestCase
9
 
 
10
 
from grackle import client
11
 
 
12
 
 
13
 
def fake_grackle_service():
14
 
    pid = os.fork()
15
 
    if pid != 0:
16
 
        return pid
17
 
    service = HTTPServer(('', 8435), BaseHTTPRequestHandler)
18
 
    service.handle_request()
19
 
    os._exit(0)
 
16
from urlparse import urlparse
 
17
 
 
18
from testtools import ExpectedException
 
19
 
 
20
from grackle.client import (
 
21
    GrackleClient,
 
22
    UnparsableDateRange,
 
23
    UnsupportedDisplayType,
 
24
    UnsupportedOrder,
 
25
    )
 
26
from grackle.store import (
 
27
    make_json_message,
 
28
    MemoryStore,
 
29
    )
 
30
 
 
31
 
 
32
def make_message(message_id, body='body', headers=None, hidden=False):
 
33
    if headers is None:
 
34
        headers = {}
 
35
    message_headers = {
 
36
        'Message-Id': message_id,
 
37
        'date': '2005-01-01',
 
38
        'subject': 'subject',
 
39
        'from': 'author',
 
40
        'replies': '',
 
41
        }
 
42
    message_headers.update(headers.items())
 
43
    message = Message()
 
44
    message.set_payload(body)
 
45
    for key, value in message_headers.items():
 
46
        message[key] = value
 
47
    return make_json_message(message_id, message.as_string(), hidden)
 
48
 
 
49
 
 
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)
 
61
 
 
62
 
 
63
class ForkedFakeService:
 
64
    """A Grackle service fake, as a ContextManager."""
 
65
 
 
66
    def __init__(self, port, message_archives=None, write_logs=False):
 
67
        """Constructor.
 
68
 
 
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.
 
74
        """
 
75
        self.pid = None
 
76
        self.port = port
 
77
        if message_archives is None:
 
78
            self.message_archives = {}
 
79
        else:
 
80
            self.message_archives = message_archives
 
81
        self.read_end, self.write_end = os.pipe()
 
82
        self.write_logs = write_logs
 
83
 
 
84
    @staticmethod
 
85
    def from_client(client, message_archives=None):
 
86
        """Instantiate a ForkedFakeService from the client.
 
87
 
 
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.
 
92
        """
 
93
        return ForkedFakeService(client.port, message_archives)
 
94
 
 
95
    def is_ready(self):
 
96
        """Tell the parent process that the server is ready for writes."""
 
97
        os.write(self.write_end, 'asdf')
 
98
 
 
99
    def __enter__(self):
 
100
        """Run the service.
 
101
 
 
102
        Fork and start a server in the child.  Return when the server is ready
 
103
        for use."""
 
104
        pid = os.fork()
 
105
        if pid == 0:
 
106
            self.start_server()
 
107
        self.pid = pid
 
108
        os.read(self.read_end, 1)
 
109
        return
 
110
 
 
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', {})
 
118
        self.is_ready()
 
119
        if self.write_logs:
 
120
            logging.basicConfig(
 
121
                stream=sys.stderr, level=logging.INFO)
 
122
        service.serve_forever()
 
123
 
 
124
    def __exit__(self, exc_type, exc_val, traceback):
 
125
        os.kill(self.pid, SIGKILL)
 
126
 
 
127
 
 
128
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
 
129
    """A request handler that forwards to server.store."""
 
130
 
 
131
    def __init__(self, *args, **kwargs):
 
132
        """Constructor.  Sets up logging."""
 
133
        self.logger = logging.getLogger('http')
 
134
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
 
135
 
 
136
    def do_POST(self):
 
137
        """Create a message on POST."""
 
138
        message = self.rfile.read(int(self.headers['content-length']))
 
139
        scheme, netloc, path, params, query_string, fragments = (
 
140
            urlparse(self.path))
 
141
        parts = path.split('/')
 
142
        if parts[1] == 'archive' and len(parts) == 4:
 
143
            try:
 
144
                # This expected path is /archive/archive_id/message_id.
 
145
                self.server.store.put_message(parts[2], parts[3], message)
 
146
                self.send_response(httplib.CREATED)
 
147
                self.end_headers()
 
148
                self.wfile.close()
 
149
            except:
 
150
                self.send_error(httplib.BAD_REQUEST)
 
151
 
 
152
    def do_GET(self):
 
153
        """Retrieve a list of messages on GET."""
 
154
        scheme, netloc, path, params, query_string, fragments = (
 
155
            urlparse(self.path))
 
156
        parts = path.split('/')
 
157
        if parts[1] == 'archive':
 
158
            try:
 
159
                response = self.server.store.get_messages(
 
160
                    parts[2], query_string)
 
161
                self.send_response(httplib.OK)
 
162
                self.end_headers()
 
163
                self.wfile.write(simplejson.dumps(response))
 
164
            except Exception, error:
 
165
                self.send_response(
 
166
                    httplib.BAD_REQUEST, error.__doc__)
 
167
                return
 
168
 
 
169
    def log_message(self, format, *args):
 
170
        """Override log_message to use standard Python logging."""
 
171
        message = "%s - - [%s] %s\n" % (
 
172
            self.address_string(), self.log_date_time_string(), format % args)
 
173
        self.logger.info(message)
20
174
 
21
175
 
22
176
class TestPutMessage(TestCase):
23
177
 
24
178
    def test_put_message(self):
25
 
        service = fake_grackle_service()
26
 
        client.put_message('arch1', StringIO('This is a message'))
 
179
        client = GrackleClient('localhost', 8420)
 
180
        message_archives = {'arch1': []}
 
181
        with ForkedFakeService.from_client(client, message_archives):
 
182
            client.put_message('arch1', 'id1', StringIO('This is a message'))
 
183
            response = client.get_messages('arch1')
 
184
        self.assertEqual(1, len(response['messages']))
 
185
        message = response['messages'][0]
 
186
        self.assertEqual('id1', message['message_id'])
 
187
 
 
188
    def test_put_message_without_archive(self):
 
189
        client = GrackleClient('localhost', 8421)
 
190
        message_archives = {'arch1': []}
 
191
        with ForkedFakeService.from_client(client, message_archives):
 
192
            with ExpectedException(Exception, 'wtf'):
 
193
                client.put_message('no-archive', 'id1', StringIO('message'))
 
194
 
 
195
 
 
196
class TestGetMessages(TestCase):
 
197
 
 
198
    def assertIDOrder(self, ids, messages):
 
199
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
200
 
 
201
    def assertMessageIDs(self, ids, messages):
 
202
        self.assertIDOrder(
 
203
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
204
 
 
205
    def test_get_messages(self):
 
206
        client = GrackleClient('localhost', 8430)
 
207
        archive = {
 
208
            'baz': [make_message('foo'), make_message('bar')]}
 
209
        with ForkedFakeService.from_client(client, archive):
 
210
            response = client.get_messages('baz')
 
211
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
 
212
            response['messages']))
 
213
        self.assertIs(None, response['next_memo'])
 
214
        self.assertIs(None, response['previous_memo'])
 
215
 
 
216
    def test_get_messages_by_id(self):
 
217
        client = GrackleClient('localhost', 8437)
 
218
        archive = {
 
219
            'baz': [make_message('foo'), make_message('bar')]}
 
220
        with ForkedFakeService.from_client(client, archive):
 
221
            response = client.get_messages('baz', message_ids=['foo'])
 
222
        message, = response['messages']
 
223
        self.assertEqual('foo', message['message_id'])
 
224
 
 
225
    def test_get_messages_batching(self):
 
226
        client = GrackleClient('localhost', 8438)
 
227
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
228
        with ForkedFakeService.from_client(client, archive):
 
229
            response = client.get_messages('baz', limit=1)
 
230
            self.assertEqual(1, len(response['messages']))
 
231
            messages = response['messages']
 
232
            response = client.get_messages(
 
233
                'baz', limit=1, memo=response['next_memo'])
 
234
            self.assertEqual(1, len(response['messages']))
 
235
            messages.extend(response['messages'])
 
236
            self.assertMessageIDs(['foo', 'bar'], messages)
 
237
 
 
238
    def get_messages_member_order_test(self, key):
 
239
        client = GrackleClient('localhost', 8439)
 
240
        if key == 'author':
 
241
            header_name = 'from'
 
242
        else:
 
243
            header_name = key
 
244
        archive = {
 
245
            'baz': [
 
246
                make_message('foo', headers={header_name: '2011-03-25'}),
 
247
                make_message('bar', headers={header_name: '2011-03-24'}),
 
248
             ]}
 
249
        with ForkedFakeService.from_client(client, archive):
 
250
            response = client.get_messages('baz')
 
251
            self.assertIDOrder(['foo', 'bar'], response['messages'])
 
252
            response = client.get_messages('baz', order=key)
 
253
            self.assertIDOrder(['bar', 'foo'], response['messages'])
 
254
 
 
255
    def test_get_messages_date_order(self):
 
256
        self.get_messages_member_order_test('date')
 
257
 
 
258
    def test_get_messages_author_order(self):
 
259
        self.get_messages_member_order_test('author')
 
260
 
 
261
    def test_get_messages_subject_order(self):
 
262
        self.get_messages_member_order_test('subject')
 
263
 
 
264
    def test_get_messages_thread_subject_order(self):
 
265
        archive = {
 
266
            'baz': [
 
267
                make_message('bar', headers={'subject': 'y'}),
 
268
                make_message('qux', headers={'subject': 'z'}),
 
269
                make_message('foo', headers={'subject': 'x',
 
270
                                             'in-reply-to': 'qux'}),
 
271
             ]}
 
272
        client = GrackleClient('localhost', 8439)
 
273
        with ForkedFakeService.from_client(client, archive):
 
274
            response = client.get_messages('baz')
 
275
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
276
            response = client.get_messages('baz', order='subject')
 
277
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
 
278
            response = client.get_messages('baz', order='thread_subject')
 
279
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
280
 
 
281
    def test_get_messages_thread_oldest_order(self):
 
282
        client = GrackleClient('localhost', 8439)
 
283
        archive = {
 
284
            'baz': [
 
285
                make_message('bar', headers={'date': 'x'}),
 
286
                make_message('qux', headers={'date': 'z'}),
 
287
                make_message('foo', headers={'date': 'y',
 
288
                                             'in-reply-to': 'qux'}),
 
289
            ]}
 
290
        with ForkedFakeService.from_client(client, archive):
 
291
            response = client.get_messages('baz')
 
292
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
293
            response = client.get_messages('baz', order='date')
 
294
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
 
295
            response = client.get_messages('baz', order='thread_oldest')
 
296
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
297
 
 
298
    def test_get_messages_thread_newest_order(self):
 
299
        client = GrackleClient('localhost', 8439)
 
300
        archive = {
 
301
            'baz': [
 
302
                make_message('bar', headers={'date': 'x'}),
 
303
                make_message('qux', headers={'date': 'w'}),
 
304
                make_message('foo', headers={'date': 'y',
 
305
                                             'in-reply-to': 'bar'}),
 
306
                make_message('baz', headers={'date': 'z',
 
307
                                             'in-reply-to': 'qux'}),
 
308
            ]}
 
309
        with ForkedFakeService.from_client(client, archive):
 
310
            response = client.get_messages('baz', order='date')
 
311
            self.assertIDOrder(
 
312
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
313
            response = client.get_messages('baz', order='thread_newest')
 
314
            self.assertIDOrder(
 
315
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
316
 
 
317
    def test_get_messages_unsupported_order(self):
 
318
        client = GrackleClient('localhost', 8439)
 
319
        archive = {
 
320
            'baz': [
 
321
                make_message('foo', headers={'date': '2011-03-25'}),
 
322
                make_message('foo', headers={'date': '2011-03-24'}),
 
323
            ]}
 
324
        with ForkedFakeService.from_client(client, archive):
 
325
            with ExpectedException(UnsupportedOrder, ''):
 
326
                client.get_messages('baz', order='nonsense')
 
327
 
 
328
    def test_get_messages_headers_no_headers(self):
 
329
        client = GrackleClient('localhost', 8440)
 
330
        archive = {'baz': [make_message('foo')]}
 
331
        with ForkedFakeService.from_client(client, archive):
 
332
            response = client.get_messages('baz', headers=[
 
333
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
334
        first_message = response['messages'][0]
 
335
        self.assertEqual('foo', first_message['message_id'])
 
336
        self.assertEqual({}, first_message['headers'])
 
337
 
 
338
    def test_get_messages_headers_exclude_headers(self):
 
339
        client = GrackleClient('localhost', 8441)
 
340
        archive = {
 
341
            'baz': [make_message('foo', headers={'From': 'me'})]}
 
342
        with ForkedFakeService.from_client(client, archive):
 
343
            response = client.get_messages('baz', headers=[
 
344
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
345
        first_message = response['messages'][0]
 
346
        self.assertEqual('foo', first_message['message_id'])
 
347
        self.assertEqual({}, first_message['headers'])
 
348
 
 
349
    def test_get_messages_headers_include_headers(self):
 
350
        client = GrackleClient('localhost', 8442)
 
351
        archive = {
 
352
            'baz': [
 
353
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
 
354
        with ForkedFakeService.from_client(client, archive):
 
355
            response = client.get_messages('baz', headers=[
 
356
                'From', 'To'])
 
357
        first_message = response['messages'][0]
 
358
        self.assertEqual('foo', first_message['message_id'])
 
359
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
360
 
 
361
    def test_get_messages_max_body_length(self):
 
362
        client = GrackleClient('localhost', 8443)
 
363
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
364
        with ForkedFakeService.from_client(client, archive):
 
365
            response = client.get_messages('baz', max_body_length=3)
 
366
        first_message = response['messages'][0]
 
367
        self.assertEqual('abc', first_message['body'])
 
368
 
 
369
    def test_include_hidden(self):
 
370
        client = GrackleClient('localhost', 8444)
 
371
        archive = {
 
372
            'baz': [
 
373
                make_message('foo', hidden=True),
 
374
                make_message('bar', hidden=False),
 
375
            ]}
 
376
        with ForkedFakeService.from_client(client, archive):
 
377
            response = client.get_messages('baz', include_hidden=True)
 
378
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
 
379
            response = client.get_messages('baz', include_hidden=False)
 
380
            self.assertMessageIDs(['bar'], response['messages'])
 
381
 
 
382
    def test_display_type_unknown_value(self):
 
383
        client = GrackleClient('localhost', 8445)
 
384
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
385
        with ForkedFakeService.from_client(client, archive):
 
386
            with ExpectedException(UnsupportedDisplayType, ''):
 
387
                client.get_messages('baz', display_type='unknown')
 
388
 
 
389
    def test_display_type_headers_only(self):
 
390
        client = GrackleClient('localhost', 8446)
 
391
        archive = {
 
392
            'baz': [
 
393
                make_message('foo', body=u'abcdefghi',
 
394
                             headers={'From': 'me', 'To': 'you'})]}
 
395
        with ForkedFakeService.from_client(client, archive):
 
396
            response = client.get_messages('baz', display_type='headers-only')
 
397
        first_message = response['messages'][0]
 
398
        self.assertEqual('foo', first_message['message_id'])
 
399
        self.assertEqual(
 
400
            archive['baz'][0]['headers'], first_message['headers'])
 
401
        self.assertNotIn('body', first_message)
 
402
 
 
403
    def test_display_type_text_only(self):
 
404
        client = GrackleClient('localhost', 8446)
 
405
        archive = {
 
406
            'baz': [
 
407
                make_mime_message(
 
408
                    'foo', 'abcdefghi',
 
409
                    headers={'From': 'me', 'To': 'you'},
 
410
                    attachment_type='text/x-diff')]}
 
411
        with ForkedFakeService.from_client(client, archive):
 
412
            response = client.get_messages('baz', display_type='text-only')
 
413
        first_message = response['messages'][0]
 
414
        self.assertEqual('foo', first_message['message_id'])
 
415
        self.assertEqual('me', first_message['headers']['From'])
 
416
        self.assertEqual('you', first_message['headers']['To'])
 
417
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
418
 
 
419
    def test_display_type_all(self):
 
420
        client = GrackleClient('localhost', 8447)
 
421
        archive = {
 
422
            'baz': [
 
423
                make_mime_message(
 
424
                    'foo', 'abcdefghi',
 
425
                    headers={'From': 'me', 'To': 'you'},
 
426
                    attachment_type='text/x-diff')]}
 
427
        with ForkedFakeService.from_client(client, archive):
 
428
            response = client.get_messages('baz', display_type='all')
 
429
        first_message = response['messages'][0]
 
430
        self.assertEqual('foo', first_message['message_id'])
 
431
        self.assertEqual('me', first_message['headers']['From'])
 
432
        self.assertEqual('you', first_message['headers']['To'])
 
433
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
434
 
 
435
    def test_date_range(self):
 
436
        client = GrackleClient('localhost', 8448)
 
437
        archive = {
 
438
            'baz': [
 
439
                make_mime_message(
 
440
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
 
441
                make_mime_message(
 
442
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
 
443
                make_mime_message(
 
444
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
 
445
                make_mime_message(
 
446
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
 
447
                make_mime_message(
 
448
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
 
449
                    ]}
 
450
        with ForkedFakeService.from_client(client, archive):
 
451
            response = client.get_messages(
 
452
                'baz', date_range='2012-01-01..2012-01-31')
 
453
        ids = sorted(m['message_id'] for m in response['messages'])
 
454
        self.assertEqual(['bar', 'naf', 'qux'], ids)
 
455
 
 
456
    def test_date_range_unparsabledaterange(self):
 
457
        client = GrackleClient('localhost', 8449)
 
458
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
459
        with ForkedFakeService.from_client(client, archive):
 
460
            with ExpectedException(UnparsableDateRange, ''):
 
461
                client.get_messages('baz', date_range='2012-01-01')
 
462
 
 
463
    def test_date_range_unparsabledaterange_missing_part(self):
 
464
        client = GrackleClient('localhost', 8450)
 
465
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
466
        with ForkedFakeService.from_client(client, archive):
 
467
            with ExpectedException(UnparsableDateRange, ''):
 
468
                client.get_messages('baz', date_range='2012-01-01..')
 
469
 
 
470
    def test_date_range_unparsabledaterange_extra_part(self):
 
471
        client = GrackleClient('localhost', 8451)
 
472
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
473
        with ForkedFakeService.from_client(client, archive):
 
474
            with ExpectedException(UnparsableDateRange, ''):
 
475
                client.get_messages('baz', date_range='2012-01..12-02..12-03')