~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-02-24 21:31:01 UTC
  • Revision ID: curtis.hovey@canonical.com-20120224213101-jac3b2aj4nd5uwpj
Separate gracle MemoryStore to its own module.

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
8
12
import simplejson
9
13
from StringIO import StringIO
 
14
import sys
10
15
from unittest import TestCase
11
16
from urlparse import urlparse
12
 
from urlparse import parse_qs
13
17
 
14
18
from testtools import ExpectedException
15
19
 
16
20
from grackle.client import (
17
21
    GrackleClient,
18
 
    )
19
 
 
20
 
 
21
 
class ForkedFake:
22
 
 
23
 
    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, messages=None, write_logs=False):
 
67
        """Constructor.
 
68
 
 
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.
 
74
        """
24
75
        self.pid = None
25
76
        self.port = port
26
 
        self.messages = messages
 
77
        if messages is None:
 
78
            self.messages = {}
 
79
        else:
 
80
            self.messages = messages
27
81
        self.read_end, self.write_end = os.pipe()
 
82
        self.write_logs = write_logs
 
83
 
 
84
    @staticmethod
 
85
    def from_client(client, messages=None):
 
86
        """Instantiate a ForkedFakeService from the client.
 
87
 
 
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.
 
92
        """
 
93
        return ForkedFakeService(client.port, messages)
28
94
 
29
95
    def is_ready(self):
 
96
        """Tell the parent process that the server is ready for writes."""
30
97
        os.write(self.write_end, 'asdf')
31
98
 
32
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."""
33
104
        pid = os.fork()
34
105
        if pid == 0:
35
106
            self.start_server()
38
109
        return
39
110
 
40
111
    def start_server(self):
 
112
        """Start the HTTP server."""
41
113
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
42
 
        service.messages = self.messages
 
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', {})
43
118
        self.is_ready()
 
119
        if self.write_logs:
 
120
            logging.basicConfig(
 
121
                stream=sys.stderr, level=logging.INFO)
44
122
        service.serve_forever()
45
123
 
46
124
    def __exit__(self, exc_type, exc_val, traceback):
48
126
 
49
127
 
50
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)
51
135
 
52
136
    def do_POST(self):
 
137
        """Create a message on POST."""
53
138
        message = self.rfile.read(int(self.headers['content-length']))
54
139
        if message == 'This is a message':
55
140
            self.send_response(httplib.CREATED)
59
144
            self.send_error(httplib.BAD_REQUEST)
60
145
 
61
146
    def do_GET(self):
 
147
        """Retrieve a list of messages on GET."""
62
148
        scheme, netloc, path, params, query_string, fragments = (
63
149
            urlparse(self.path))
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
 
        limit = parameters.get('limit', 100)
73
 
        memo = parameters.get('memo')
74
 
        message_id_indices = dict(
75
 
            (m['message_id'], idx) for idx, m in enumerate(messages))
76
 
        if memo is None:
77
 
            start = 0
78
 
        else:
79
 
            start = message_id_indices[memo.encode('rot13')]
80
 
        if start > 0:
81
 
            previous_memo = messages[start - 1]['message_id'].encode('rot13')
82
 
        else:
83
 
            previous_memo = None
84
 
        end = min(start + limit, len(messages))
85
 
        if end < len(messages):
86
 
            next_memo = messages[end]['message_id'].encode('rot13')
87
 
        else:
88
 
            next_memo = None
89
 
        messages = messages[start:end]
90
 
        response = {
91
 
            'messages': messages,
92
 
            'next_memo': next_memo,
93
 
            'previous_memo': previous_memo
94
 
            }
95
 
        self.wfile.write(simplejson.dumps(response))
96
 
 
97
 
 
98
 
def fake_grackle_service(client, messages=None):
99
 
    if messages is None:
100
 
        messages = {}
101
 
    return ForkedFake(client.port, messages)
 
150
        parts = path.split('/')
 
151
        if parts[1] == 'archive':
 
152
            try:
 
153
                response = self.server.store.get_messages(
 
154
                    parts[2], query_string)
 
155
                self.send_response(httplib.OK)
 
156
                self.end_headers()
 
157
                self.wfile.write(simplejson.dumps(response))
 
158
            except Exception, error:
 
159
                self.send_response(
 
160
                    httplib.BAD_REQUEST, error.__doc__)
 
161
                return
 
162
 
 
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)
102
168
 
103
169
 
104
170
class TestPutMessage(TestCase):
105
171
 
106
172
    def test_put_message(self):
107
 
        client = GrackleClient('localhost', 8436)
108
 
        with fake_grackle_service(client):
 
173
        client = GrackleClient('localhost', 8420)
 
174
        with ForkedFakeService.from_client(client):
109
175
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
110
176
            with ExpectedException(Exception, 'wtf'):
111
177
                client.put_message('arch1', 'asdf',
114
180
 
115
181
class TestGetMessages(TestCase):
116
182
 
 
183
    def assertIDOrder(self, ids, messages):
 
184
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
185
 
117
186
    def assertMessageIDs(self, ids, messages):
118
 
        self.assertEqual(
119
 
            sorted(ids), sorted(m['message_id'] for m in messages))
 
187
        self.assertIDOrder(
 
188
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
120
189
 
121
190
    def test_get_messages(self):
122
 
        client = GrackleClient('localhost', 8435)
123
 
        with fake_grackle_service(client,
124
 
            {'baz':
125
 
            [{'message_id': 'foo'},
126
 
             {'message_id': 'bar'}]}):
 
191
        client = GrackleClient('localhost', 8430)
 
192
        archive = {
 
193
            'baz': [make_message('foo'), make_message('bar')]}
 
194
        with ForkedFakeService.from_client(client, archive):
127
195
            response = client.get_messages('baz')
128
196
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
129
197
            response['messages']))
132
200
 
133
201
    def test_get_messages_by_id(self):
134
202
        client = GrackleClient('localhost', 8437)
135
 
        with fake_grackle_service(client,
136
 
            {'baz':
137
 
            [{'message_id': 'foo'},
138
 
             {'message_id': 'bar'}]}):
 
203
        archive = {
 
204
            'baz': [make_message('foo'), make_message('bar')]}
 
205
        with ForkedFakeService.from_client(client, archive):
139
206
            response = client.get_messages('baz', message_ids=['foo'])
140
207
        message, = response['messages']
141
208
        self.assertEqual('foo', message['message_id'])
142
209
 
143
210
    def test_get_messages_batching(self):
144
 
        client = GrackleClient('localhost', 8437)
145
 
        with fake_grackle_service(client,
146
 
            {'baz':
147
 
            [{'message_id': 'foo'},
148
 
             {'message_id': 'bar'}]}):
 
211
        client = GrackleClient('localhost', 8438)
 
212
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
213
        with ForkedFakeService.from_client(client, archive):
149
214
            response = client.get_messages('baz', limit=1)
150
215
            self.assertEqual(1, len(response['messages']))
151
216
            messages = response['messages']
154
219
            self.assertEqual(1, len(response['messages']))
155
220
            messages.extend(response['messages'])
156
221
            self.assertMessageIDs(['foo', 'bar'], messages)
 
222
 
 
223
    def get_messages_member_order_test(self, key):
 
224
        client = GrackleClient('localhost', 8439)
 
225
        archive = {
 
226
            'baz': [
 
227
                make_message('foo', headers={key: '2011-03-25'}),
 
228
                make_message('bar', headers={key: '2011-03-24'}),
 
229
             ]}
 
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'])
 
235
 
 
236
    def test_get_messages_date_order(self):
 
237
        self.get_messages_member_order_test('date')
 
238
 
 
239
    def test_get_messages_author_order(self):
 
240
        self.get_messages_member_order_test('author')
 
241
 
 
242
    def test_get_messages_subject_order(self):
 
243
        self.get_messages_member_order_test('subject')
 
244
 
 
245
    def test_get_messages_thread_subject_order(self):
 
246
        archive = {
 
247
            'baz': [
 
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'}),
 
252
             ]}
 
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'])
 
261
 
 
262
    def test_get_messages_thread_oldest_order(self):
 
263
        client = GrackleClient('localhost', 8439)
 
264
        archive = {
 
265
            'baz': [
 
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'}),
 
270
            ]}
 
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'])
 
278
 
 
279
    def test_get_messages_thread_newest_order(self):
 
280
        client = GrackleClient('localhost', 8439)
 
281
        archive = {
 
282
            'baz': [
 
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'}),
 
289
            ]}
 
290
        with ForkedFakeService.from_client(client, archive):
 
291
            response = client.get_messages('baz', order='date')
 
292
            self.assertIDOrder(
 
293
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
294
            response = client.get_messages('baz', order='thread_newest')
 
295
            self.assertIDOrder(
 
296
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
297
 
 
298
    def test_get_messages_unsupported_order(self):
 
299
        client = GrackleClient('localhost', 8439)
 
300
        archive = {
 
301
            'baz': [
 
302
                make_message('foo', headers={'date': '2011-03-25'}),
 
303
                make_message('foo', headers={'date': '2011-03-24'}),
 
304
            ]}
 
305
        with ForkedFakeService.from_client(client, archive):
 
306
            with ExpectedException(UnsupportedOrder, ''):
 
307
                client.get_messages('baz', order='nonsense')
 
308
 
 
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'])
 
318
 
 
319
    def test_get_messages_headers_exclude_headers(self):
 
320
        client = GrackleClient('localhost', 8441)
 
321
        archive = {
 
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'])
 
329
 
 
330
    def test_get_messages_headers_include_headers(self):
 
331
        client = GrackleClient('localhost', 8442)
 
332
        archive = {
 
333
            'baz': [
 
334
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
 
335
        with ForkedFakeService.from_client(client, archive):
 
336
            response = client.get_messages('baz', headers=[
 
337
                'From', 'To'])
 
338
        first_message = response['messages'][0]
 
339
        self.assertEqual('foo', first_message['message_id'])
 
340
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
341
 
 
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'])
 
349
 
 
350
    def test_include_hidden(self):
 
351
        client = GrackleClient('localhost', 8444)
 
352
        archive = {
 
353
            'baz': [
 
354
                make_message('foo', hidden=True),
 
355
                make_message('bar', hidden=False),
 
356
            ]}
 
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'])
 
362
 
 
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')
 
369
 
 
370
    def test_display_type_headers_only(self):
 
371
        client = GrackleClient('localhost', 8446)
 
372
        archive = {
 
373
            'baz': [
 
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'])
 
380
        self.assertEqual(
 
381
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
 
382
            first_message['headers'])
 
383
        self.assertNotIn('body', first_message)
 
384
 
 
385
    def test_display_type_text_only(self):
 
386
        client = GrackleClient('localhost', 8446)
 
387
        archive = {
 
388
            'baz': [
 
389
                make_mime_message(
 
390
                    'foo', 'abcdefghi',
 
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'])
 
400
 
 
401
    def test_display_type_all(self):
 
402
        client = GrackleClient('localhost', 8447)
 
403
        archive = {
 
404
            'baz': [
 
405
                make_mime_message(
 
406
                    'foo', 'abcdefghi',
 
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'])
 
415
        self.assertEqual(
 
416
            'abcdefghi\n\nattactment data.', first_message['body'])
 
417
 
 
418
    def test_date_range(self):
 
419
        client = GrackleClient('localhost', 8448)
 
420
        archive = {
 
421
            'baz': [
 
422
                make_mime_message(
 
423
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
 
424
                make_mime_message(
 
425
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
 
426
                make_mime_message(
 
427
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
 
428
                make_mime_message(
 
429
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
 
430
                make_mime_message(
 
431
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
 
432
                    ]}
 
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)
 
438
 
 
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')
 
445
 
 
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..')
 
452
 
 
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')