~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-12 09:37:51 UTC
  • Revision ID: aaron@canonical.com-20120112093751-ltzymm8dbk34vixq
ImplementĀ memo/limitĀ support.

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
 
    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
 
        """
 
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 messages is None:
78
 
            self.messages = {}
79
 
        else:
80
 
            self.messages = messages
 
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, 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)
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.messages)
115
 
        for archive_id, messages in service.store.messages.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
54
        if message == 'This is a message':
140
55
            self.send_response(httplib.CREATED)
144
59
            self.send_error(httplib.BAD_REQUEST)
145
60
 
146
61
    def do_GET(self):
147
 
        """Retrieve a list of messages on GET."""
148
62
        scheme, netloc, path, params, query_string, fragments = (
149
63
            urlparse(self.path))
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)
 
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)
168
102
 
169
103
 
170
104
class TestPutMessage(TestCase):
171
105
 
172
106
    def test_put_message(self):
173
 
        client = GrackleClient('localhost', 8420)
174
 
        with ForkedFakeService.from_client(client):
 
107
        client = GrackleClient('localhost', 8436)
 
108
        with fake_grackle_service(client):
175
109
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
176
110
            with ExpectedException(Exception, 'wtf'):
177
111
                client.put_message('arch1', 'asdf',
180
114
 
181
115
class TestGetMessages(TestCase):
182
116
 
183
 
    def assertIDOrder(self, ids, messages):
184
 
        self.assertEqual(ids, [m['message_id'] for m in messages])
185
 
 
186
117
    def assertMessageIDs(self, ids, messages):
187
 
        self.assertIDOrder(
188
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
118
        self.assertEqual(
 
119
            sorted(ids), sorted(m['message_id'] for m in messages))
189
120
 
190
121
    def test_get_messages(self):
191
 
        client = GrackleClient('localhost', 8430)
192
 
        archive = {
193
 
            'baz': [make_message('foo'), make_message('bar')]}
194
 
        with ForkedFakeService.from_client(client, archive):
 
122
        client = GrackleClient('localhost', 8435)
 
123
        with fake_grackle_service(client,
 
124
            {'baz':
 
125
            [{'message_id': 'foo'},
 
126
             {'message_id': 'bar'}]}):
195
127
            response = client.get_messages('baz')
196
128
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
197
129
            response['messages']))
200
132
 
201
133
    def test_get_messages_by_id(self):
202
134
        client = GrackleClient('localhost', 8437)
203
 
        archive = {
204
 
            'baz': [make_message('foo'), make_message('bar')]}
205
 
        with ForkedFakeService.from_client(client, archive):
 
135
        with fake_grackle_service(client,
 
136
            {'baz':
 
137
            [{'message_id': 'foo'},
 
138
             {'message_id': 'bar'}]}):
206
139
            response = client.get_messages('baz', message_ids=['foo'])
207
140
        message, = response['messages']
208
141
        self.assertEqual('foo', message['message_id'])
209
142
 
210
143
    def test_get_messages_batching(self):
211
 
        client = GrackleClient('localhost', 8438)
212
 
        archive = {'baz': [make_message('foo'), make_message('bar')]}
213
 
        with ForkedFakeService.from_client(client, archive):
 
144
        client = GrackleClient('localhost', 8437)
 
145
        with fake_grackle_service(client,
 
146
            {'baz':
 
147
            [{'message_id': 'foo'},
 
148
             {'message_id': 'bar'}]}):
214
149
            response = client.get_messages('baz', limit=1)
215
150
            self.assertEqual(1, len(response['messages']))
216
151
            messages = response['messages']
219
154
            self.assertEqual(1, len(response['messages']))
220
155
            messages.extend(response['messages'])
221
156
            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')