~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-02-29 23:55:28 UTC
  • Revision ID: curtis.hovey@canonical.com-20120229235528-7wphq02b2y9vt7ni
Implemented a partial put into the MemoryStore.

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
5
8
import httplib
 
9
import logging
6
10
import os
7
11
from signal import SIGKILL
 
12
import simplejson
8
13
from StringIO import StringIO
 
14
import sys
9
15
from unittest import TestCase
 
16
from urlparse import urlparse
10
17
 
11
18
from testtools import ExpectedException
12
19
 
13
20
from grackle.client import (
14
21
    GrackleClient,
15
 
    )
16
 
 
17
 
 
18
 
class ForkedFake:
19
 
 
20
 
    def __init__(self, port, messages=None):
 
22
    UnparsableDateRange,
 
23
    UnsupportedDisplayType,
 
24
    UnsupportedOrder,
 
25
    )
 
26
from grackle.store import (
 
27
    MemoryStore,
 
28
    )
 
29
 
 
30
 
 
31
def make_message(message_id, body='body', headers=None, hidden=False):
 
32
    if headers is None:
 
33
        headers = {}
 
34
    headers['Message-Id'] = message_id
 
35
    message = {
 
36
        'message_id': message_id,
 
37
        'headers': headers,
 
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'),
 
42
        'hidden': hidden,
 
43
        'attachments': [],
 
44
        'replies': headers.get('in-reply-to', None),
 
45
        'body': body,
 
46
        }
 
47
    return message
 
48
 
 
49
 
 
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)
 
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
        """
21
75
        self.pid = None
22
76
        self.port = port
23
 
        self.messages = messages
 
77
        if message_archives is None:
 
78
            self.message_archives = {}
 
79
        else:
 
80
            self.message_archives = message_archives
24
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)
25
94
 
26
95
    def is_ready(self):
 
96
        """Tell the parent process that the server is ready for writes."""
27
97
        os.write(self.write_end, 'asdf')
28
98
 
29
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."""
30
104
        pid = os.fork()
31
105
        if pid == 0:
32
106
            self.start_server()
35
109
        return
36
110
 
37
111
    def start_server(self):
 
112
        """Start the HTTP server."""
38
113
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
39
 
        service.messages = self.messages
 
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', {})
40
118
        self.is_ready()
 
119
        if self.write_logs:
 
120
            logging.basicConfig(
 
121
                stream=sys.stderr, level=logging.INFO)
41
122
        service.serve_forever()
42
123
 
43
124
    def __exit__(self, exc_type, exc_val, traceback):
45
126
 
46
127
 
47
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)
48
135
 
49
136
    def do_POST(self):
 
137
        """Create a message on POST."""
50
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':
 
143
            # This expected path is /archive/archive_id/message_id.
 
144
            self.server.store.put_message(parts[2], parts[3], message)
51
145
        if message == 'This is a message':
52
146
            self.send_response(httplib.CREATED)
53
147
            self.end_headers()
55
149
        else:
56
150
            self.send_error(httplib.BAD_REQUEST)
57
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
58
168
 
59
 
def fake_grackle_service(client, messages=None):
60
 
    if messages is None:
61
 
        messages = {}
62
 
    return ForkedFake(client.port, messages)
 
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)
63
174
 
64
175
 
65
176
class TestPutMessage(TestCase):
66
177
 
67
178
    def test_put_message(self):
68
 
        client = GrackleClient('localhost', 8436)
69
 
        with fake_grackle_service(client):
70
 
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
 
179
        client = GrackleClient('localhost', 8420)
 
180
        message_archives = {'arch1': []}
 
181
        service = ForkedFakeService.from_client(client, message_archives)
 
182
        with service:
 
183
            client.put_message('arch1', 'id1', StringIO('This is a message'))
 
184
            response = client.get_messages('arch1')
 
185
            self.assertEqual(1, len(response['messages']))
 
186
            message = response['messages'][0]
 
187
            self.assertEqual('id1', message['message_id'])
 
188
 
 
189
    def test_put_message_without_message(self):
 
190
        client = GrackleClient('localhost', 8421)
 
191
        message_archives = {'arch1': []}
 
192
        with ForkedFakeService.from_client(client, message_archives):
71
193
            with ExpectedException(Exception, 'wtf'):
72
 
                client.put_message('arch1', 'asdf',
 
194
                client.put_message('arch1', 'id1',
73
195
                    StringIO('This is not a message'))
74
196
 
75
197
 
76
198
class TestGetMessages(TestCase):
77
199
 
 
200
    def assertIDOrder(self, ids, messages):
 
201
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
202
 
 
203
    def assertMessageIDs(self, ids, messages):
 
204
        self.assertIDOrder(
 
205
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
206
 
78
207
    def test_get_messages(self):
79
 
        client = GrackleClient('localhost', 8435)
80
 
        with fake_grackle_service(client,
81
 
            {'baz':
82
 
            [{'message-id': 'foo'},
83
 
             {'message-id': 'bar'}]}):
84
 
            response = client.get_messages('baz')
85
 
        self.assertEqual(['bar', 'foo'], sorted(response.keys()))
 
208
        client = GrackleClient('localhost', 8430)
 
209
        archive = {
 
210
            'baz': [make_message('foo'), make_message('bar')]}
 
211
        with ForkedFakeService.from_client(client, archive):
 
212
            response = client.get_messages('baz')
 
213
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
 
214
            response['messages']))
 
215
        self.assertIs(None, response['next_memo'])
 
216
        self.assertIs(None, response['previous_memo'])
 
217
 
 
218
    def test_get_messages_by_id(self):
 
219
        client = GrackleClient('localhost', 8437)
 
220
        archive = {
 
221
            'baz': [make_message('foo'), make_message('bar')]}
 
222
        with ForkedFakeService.from_client(client, archive):
 
223
            response = client.get_messages('baz', message_ids=['foo'])
 
224
        message, = response['messages']
 
225
        self.assertEqual('foo', message['message_id'])
 
226
 
 
227
    def test_get_messages_batching(self):
 
228
        client = GrackleClient('localhost', 8438)
 
229
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
230
        with ForkedFakeService.from_client(client, archive):
 
231
            response = client.get_messages('baz', limit=1)
 
232
            self.assertEqual(1, len(response['messages']))
 
233
            messages = response['messages']
 
234
            response = client.get_messages(
 
235
                'baz', limit=1, memo=response['next_memo'])
 
236
            self.assertEqual(1, len(response['messages']))
 
237
            messages.extend(response['messages'])
 
238
            self.assertMessageIDs(['foo', 'bar'], messages)
 
239
 
 
240
    def get_messages_member_order_test(self, key):
 
241
        client = GrackleClient('localhost', 8439)
 
242
        archive = {
 
243
            'baz': [
 
244
                make_message('foo', headers={key: '2011-03-25'}),
 
245
                make_message('bar', headers={key: '2011-03-24'}),
 
246
             ]}
 
247
        with ForkedFakeService.from_client(client, archive):
 
248
            response = client.get_messages('baz')
 
249
            self.assertIDOrder(['foo', 'bar'], response['messages'])
 
250
            response = client.get_messages('baz', order=key)
 
251
            self.assertIDOrder(['bar', 'foo'], response['messages'])
 
252
 
 
253
    def test_get_messages_date_order(self):
 
254
        self.get_messages_member_order_test('date')
 
255
 
 
256
    def test_get_messages_author_order(self):
 
257
        self.get_messages_member_order_test('author')
 
258
 
 
259
    def test_get_messages_subject_order(self):
 
260
        self.get_messages_member_order_test('subject')
 
261
 
 
262
    def test_get_messages_thread_subject_order(self):
 
263
        archive = {
 
264
            'baz': [
 
265
                make_message('bar', headers={'subject': 'y'}),
 
266
                make_message('qux', headers={'subject': 'z'}),
 
267
                make_message('foo', headers={'subject': 'x',
 
268
                                             'in-reply-to': 'qux'}),
 
269
             ]}
 
270
        client = GrackleClient('localhost', 8439)
 
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='subject')
 
275
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
 
276
            response = client.get_messages('baz', order='thread_subject')
 
277
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
278
 
 
279
    def test_get_messages_thread_oldest_order(self):
 
280
        client = GrackleClient('localhost', 8439)
 
281
        archive = {
 
282
            'baz': [
 
283
                make_message('bar', headers={'date': 'x'}),
 
284
                make_message('qux', headers={'date': 'z'}),
 
285
                make_message('foo', headers={'date': 'y',
 
286
                                             'in-reply-to': 'qux'}),
 
287
            ]}
 
288
        with ForkedFakeService.from_client(client, archive):
 
289
            response = client.get_messages('baz')
 
290
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
291
            response = client.get_messages('baz', order='date')
 
292
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
 
293
            response = client.get_messages('baz', order='thread_oldest')
 
294
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
295
 
 
296
    def test_get_messages_thread_newest_order(self):
 
297
        client = GrackleClient('localhost', 8439)
 
298
        archive = {
 
299
            'baz': [
 
300
                make_message('bar', headers={'date': 'x'}),
 
301
                make_message('qux', headers={'date': 'w'}),
 
302
                make_message('foo', headers={'date': 'y',
 
303
                                             'in-reply-to': 'bar'}),
 
304
                make_message('baz', headers={'date': 'z',
 
305
                                             'in-reply-to': 'qux'}),
 
306
            ]}
 
307
        with ForkedFakeService.from_client(client, archive):
 
308
            response = client.get_messages('baz', order='date')
 
309
            self.assertIDOrder(
 
310
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
311
            response = client.get_messages('baz', order='thread_newest')
 
312
            self.assertIDOrder(
 
313
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
314
 
 
315
    def test_get_messages_unsupported_order(self):
 
316
        client = GrackleClient('localhost', 8439)
 
317
        archive = {
 
318
            'baz': [
 
319
                make_message('foo', headers={'date': '2011-03-25'}),
 
320
                make_message('foo', headers={'date': '2011-03-24'}),
 
321
            ]}
 
322
        with ForkedFakeService.from_client(client, archive):
 
323
            with ExpectedException(UnsupportedOrder, ''):
 
324
                client.get_messages('baz', order='nonsense')
 
325
 
 
326
    def test_get_messages_headers_no_headers(self):
 
327
        client = GrackleClient('localhost', 8440)
 
328
        archive = {'baz': [make_message('foo')]}
 
329
        with ForkedFakeService.from_client(client, archive):
 
330
            response = client.get_messages('baz', headers=[
 
331
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
332
        first_message = response['messages'][0]
 
333
        self.assertEqual('foo', first_message['message_id'])
 
334
        self.assertEqual({}, first_message['headers'])
 
335
 
 
336
    def test_get_messages_headers_exclude_headers(self):
 
337
        client = GrackleClient('localhost', 8441)
 
338
        archive = {
 
339
            'baz': [make_message('foo', headers={'From': 'me'})]}
 
340
        with ForkedFakeService.from_client(client, archive):
 
341
            response = client.get_messages('baz', headers=[
 
342
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
343
        first_message = response['messages'][0]
 
344
        self.assertEqual('foo', first_message['message_id'])
 
345
        self.assertEqual({}, first_message['headers'])
 
346
 
 
347
    def test_get_messages_headers_include_headers(self):
 
348
        client = GrackleClient('localhost', 8442)
 
349
        archive = {
 
350
            'baz': [
 
351
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
 
352
        with ForkedFakeService.from_client(client, archive):
 
353
            response = client.get_messages('baz', headers=[
 
354
                'From', 'To'])
 
355
        first_message = response['messages'][0]
 
356
        self.assertEqual('foo', first_message['message_id'])
 
357
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
358
 
 
359
    def test_get_messages_max_body_length(self):
 
360
        client = GrackleClient('localhost', 8443)
 
361
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
362
        with ForkedFakeService.from_client(client, archive):
 
363
            response = client.get_messages('baz', max_body_length=3)
 
364
        first_message = response['messages'][0]
 
365
        self.assertEqual('abc', first_message['body'])
 
366
 
 
367
    def test_include_hidden(self):
 
368
        client = GrackleClient('localhost', 8444)
 
369
        archive = {
 
370
            'baz': [
 
371
                make_message('foo', hidden=True),
 
372
                make_message('bar', hidden=False),
 
373
            ]}
 
374
        with ForkedFakeService.from_client(client, archive):
 
375
            response = client.get_messages('baz', include_hidden=True)
 
376
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
 
377
            response = client.get_messages('baz', include_hidden=False)
 
378
            self.assertMessageIDs(['bar'], response['messages'])
 
379
 
 
380
    def test_display_type_unknown_value(self):
 
381
        client = GrackleClient('localhost', 8445)
 
382
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
383
        with ForkedFakeService.from_client(client, archive):
 
384
            with ExpectedException(UnsupportedDisplayType, ''):
 
385
                client.get_messages('baz', display_type='unknown')
 
386
 
 
387
    def test_display_type_headers_only(self):
 
388
        client = GrackleClient('localhost', 8446)
 
389
        archive = {
 
390
            'baz': [
 
391
                make_message('foo', body=u'abcdefghi',
 
392
                             headers={'From': 'me', 'To': 'you'})]}
 
393
        with ForkedFakeService.from_client(client, archive):
 
394
            response = client.get_messages('baz', display_type='headers-only')
 
395
        first_message = response['messages'][0]
 
396
        self.assertEqual('foo', first_message['message_id'])
 
397
        self.assertEqual(
 
398
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
 
399
            first_message['headers'])
 
400
        self.assertNotIn('body', first_message)
 
401
 
 
402
    def test_display_type_text_only(self):
 
403
        client = GrackleClient('localhost', 8446)
 
404
        archive = {
 
405
            'baz': [
 
406
                make_mime_message(
 
407
                    'foo', 'abcdefghi',
 
408
                    headers={'From': 'me', 'To': 'you'},
 
409
                    attachment_type='text/x-diff')]}
 
410
        with ForkedFakeService.from_client(client, archive):
 
411
            response = client.get_messages('baz', display_type='text-only')
 
412
        first_message = response['messages'][0]
 
413
        self.assertEqual('foo', first_message['message_id'])
 
414
        self.assertEqual('me', first_message['headers']['From'])
 
415
        self.assertEqual('you', first_message['headers']['To'])
 
416
        self.assertEqual('abcdefghi', first_message['body'])
 
417
 
 
418
    def test_display_type_all(self):
 
419
        client = GrackleClient('localhost', 8447)
 
420
        archive = {
 
421
            'baz': [
 
422
                make_mime_message(
 
423
                    'foo', 'abcdefghi',
 
424
                    headers={'From': 'me', 'To': 'you'},
 
425
                    attachment_type='text/x-diff')]}
 
426
        with ForkedFakeService.from_client(client, archive):
 
427
            response = client.get_messages('baz', display_type='all')
 
428
        first_message = response['messages'][0]
 
429
        self.assertEqual('foo', first_message['message_id'])
 
430
        self.assertEqual('me', first_message['headers']['From'])
 
431
        self.assertEqual('you', first_message['headers']['To'])
 
432
        self.assertEqual(
 
433
            'abcdefghi\n\nattactment data.', 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')