~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-11 14:04:41 UTC
  • Revision ID: aaron@canonical.com-20120111140441-l4sanxq1en07oblx
Test filtering by message-id.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
    HTTPServer,
3
3
    BaseHTTPRequestHandler,
4
4
    )
5
 
from email.message import Message
6
 
from email.mime.multipart import MIMEMultipart
7
 
from email.mime.text import MIMEText
8
5
import httplib
9
 
import logging
10
6
import os
11
7
from signal import SIGKILL
12
8
import simplejson
13
9
from StringIO import StringIO
14
 
import sys
15
10
from unittest import TestCase
16
11
from urlparse import urlparse
 
12
from urlparse import parse_qs
17
13
 
18
14
from testtools import ExpectedException
19
15
 
20
16
from grackle.client import (
21
17
    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
 
        """
 
18
    )
 
19
 
 
20
 
 
21
class ForkedFake:
 
22
 
 
23
    def __init__(self, port, messages=None):
75
24
        self.pid = None
76
25
        self.port = port
77
 
        if message_archives is None:
78
 
            self.message_archives = {}
79
 
        else:
80
 
            self.message_archives = message_archives
 
26
        self.messages = messages
81
27
        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
28
 
95
29
    def is_ready(self):
96
 
        """Tell the parent process that the server is ready for writes."""
97
30
        os.write(self.write_end, 'asdf')
98
31
 
99
32
    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
33
        pid = os.fork()
105
34
        if pid == 0:
106
35
            self.start_server()
109
38
        return
110
39
 
111
40
    def start_server(self):
112
 
        """Start the HTTP server."""
113
41
        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', {})
 
42
        service.messages = self.messages
118
43
        self.is_ready()
119
 
        if self.write_logs:
120
 
            logging.basicConfig(
121
 
                stream=sys.stderr, level=logging.INFO)
122
44
        service.serve_forever()
123
45
 
124
46
    def __exit__(self, exc_type, exc_val, traceback):
126
48
 
127
49
 
128
50
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
51
 
136
52
    def do_POST(self):
137
 
        """Create a message on POST."""
138
53
        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)
 
54
        if message == 'This is a message':
 
55
            self.send_response(httplib.CREATED)
 
56
            self.end_headers()
 
57
            self.wfile.close()
 
58
        else:
 
59
            self.send_error(httplib.BAD_REQUEST)
151
60
 
152
61
    def do_GET(self):
153
 
        """Retrieve a list of messages on GET."""
154
62
        scheme, netloc, path, params, query_string, fragments = (
155
63
            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)
 
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)
 
68
        self.end_headers()
 
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
        self.wfile.write(simplejson.dumps(messages))
 
73
 
 
74
 
 
75
def fake_grackle_service(client, messages=None):
 
76
    if messages is None:
 
77
        messages = {}
 
78
    return ForkedFake(client.port, messages)
174
79
 
175
80
 
176
81
class TestPutMessage(TestCase):
177
82
 
178
83
    def test_put_message(self):
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):
 
84
        client = GrackleClient('localhost', 8436)
 
85
        with fake_grackle_service(client):
 
86
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
192
87
            with ExpectedException(Exception, 'wtf'):
193
 
                client.put_message('no-archive', 'id1', StringIO('message'))
 
88
                client.put_message('arch1', 'asdf',
 
89
                    StringIO('This is not a message'))
194
90
 
195
91
 
196
92
class TestGetMessages(TestCase):
197
93
 
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
94
    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):
 
95
        client = GrackleClient('localhost', 8435)
 
96
        with fake_grackle_service(client,
 
97
            {'baz':
 
98
            [{'message-id': 'foo'},
 
99
             {'message-id': 'bar'}]}):
210
100
            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'])
 
101
        self.assertEqual(['bar', 'foo'], sorted(m['message-id'] for m in
 
102
            response))
215
103
 
216
104
    def test_get_messages_by_id(self):
217
105
        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')
 
106
        with fake_grackle_service(client,
 
107
            {'baz':
 
108
            [{'message-id': 'foo'},
 
109
             {'message-id': 'bar'}]}):
 
110
            message, = client.get_messages('baz', message_ids=['foo'])
 
111
        self.assertEqual('foo', message['message-id'])