~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-16 16:29:12 UTC
  • Revision ID: aaron@canonical.com-20120116162912-cc760p6gn7q5qdwr
Switch test HTTP server to standard Python logging.

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
6
import logging
10
7
import os
20
17
 
21
18
from grackle.client import (
22
19
    GrackleClient,
23
 
    UnsupportedDisplayType,
24
20
    UnsupportedOrder,
25
21
    )
26
22
 
27
23
 
28
 
def make_message(message_id, body='body', headers=None, hidden=False):
29
 
    if headers is None:
30
 
        headers = {}
31
 
    headers['Message-Id'] = message_id
32
 
    message = {
33
 
        'message_id': message_id,
34
 
        'headers': headers,
35
 
        'thread_id': message_id,
36
 
        'date': headers.get('date', '2005-01-01'),
37
 
        'subject': headers.get('subject', 'subject'),
38
 
        'author': headers.get('author', 'author'),
39
 
        'hidden': hidden,
40
 
        'attachments': [],
41
 
        'replies': headers.get('in-reply-to', None),
42
 
        'body': body,
43
 
        }
44
 
    return message
45
 
 
46
 
 
47
 
def make_mime_message(message_id, body='body', headers=None, hidden=False,
48
 
                      attachment_type=None):
49
 
    message = MIMEMultipart()
50
 
    message.attach(MIMEText(body))
51
 
    if attachment_type is not None:
52
 
        attachment = Message()
53
 
        attachment.set_payload('attactment data.')
54
 
        attachment['Content-Type'] = attachment_type
55
 
        attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
56
 
        message.attach(attachment)
57
 
    return make_message(message_id, message.get_payload(), headers, hidden)
58
 
 
59
 
 
60
24
def threaded_messages(messages):
61
25
    threads = {}
62
26
    count = 0
63
27
    pending = []
64
28
    for message in messages:
65
 
        if message.get('replies') is None:
 
29
        if message.get('in_reply_to') is None:
66
30
            threads[message['message_id']] = [message]
67
31
            count += 1
68
32
        else:
69
33
            pending.append(message)
70
34
    for message in pending:
71
 
        threads[message['replies']].append(message)
 
35
        threads[message['in_reply_to']].append(message)
72
36
    return threads.values()
73
37
 
74
38
 
75
39
class GrackleStore:
76
 
    """A memory-backed message store."""
77
40
 
78
41
    def __init__(self, messages):
79
 
        """Constructor."""
80
42
        self.messages = messages
81
43
 
82
 
    @staticmethod
83
 
    def is_multipart(message):
84
 
        return isinstance(message['body'], list)
85
 
 
86
44
    def get_messages(self, archive_id, query_string):
87
 
        """Return matching messages.
88
 
 
89
 
        :param archive_id: The archive to retrieve from.
90
 
        :param query_string: Contains 'parameters', which is a JSON-format
91
 
            string describing parameters.
92
 
        """
93
45
        query = parse_qs(query_string)
94
46
        parameters = simplejson.loads(query['parameters'][0])
95
47
        order = parameters.get('order')
96
48
        messages = self.messages[archive_id]
97
 
        if order is not None:
 
49
        if order is not None :
98
50
            if order not in SUPPORTED_ORDERS:
99
51
                raise UnsupportedOrder
100
52
            elif order.startswith('thread_'):
110
62
                    messages.extend(thread)
111
63
            else:
112
64
                messages.sort(key=lambda m: m[order])
113
 
        display_type = parameters.get('display_type', 'all')
114
 
        if display_type not in SUPPORTED_DISPLAY_TYPES:
115
 
            raise UnsupportedDisplayType
116
65
        new_messages = []
117
66
        for message in messages:
118
 
            if (not parameters['include_hidden'] and message['hidden']):
 
67
            if (
 
68
                not parameters['include_hidden']
 
69
                and message.get('hidden', False)):
119
70
                continue
120
 
            if ('message_ids' in parameters
121
 
                and message['message_id'] not in parameters['message_ids']):
 
71
 
 
72
            if ('message_ids' in parameters and
 
73
                message['message_id'] not in parameters['message_ids']):
122
74
                continue
123
75
            message = dict(message)
124
76
            if 'headers' in parameters:
126
78
                    (k, v) for k, v in message['headers'].iteritems()
127
79
                    if k in parameters['headers'])
128
80
                message['headers'] = headers
129
 
            if display_type == 'headers-only':
130
 
                del message['body']
131
 
            elif display_type == 'text-only' and self.is_multipart(message):
132
 
                text_parts = [
133
 
                    part.get_payload() for part in message['body']
134
 
                    if part.get_content_type() == 'text/plain']
135
 
                message['body'] = '\n\n'.join(text_parts)
136
 
            elif display_type == 'all' and self.is_multipart(message):
137
 
                parts = [str(part.get_payload()) for part in message['body']]
138
 
                message['body'] = '\n\n'.join(parts)
139
81
            max_body = parameters.get('max_body_length')
140
 
            if max_body is not None and display_type != 'headers-only':
 
82
            if max_body is not None:
141
83
                message['body'] = message['body'][:max_body]
142
84
            new_messages.append(message)
143
85
        messages = new_messages
168
110
        return response
169
111
 
170
112
 
171
 
class ForkedFakeService:
172
 
    """A Grackle service fake, as a ContextManager."""
173
 
 
174
 
    def __init__(self, port, messages=None, write_logs=False):
175
 
        """Constructor.
176
 
 
177
 
        :param port: The tcp port to use.
178
 
        :param messages: A dict of lists of dicts representing messages.  The
179
 
            outer dict represents the archive, the list represents the list of
180
 
            messages for that archive.
181
 
        :param write_logs: If true, log messages will be written to stdout.
182
 
        """
 
113
 
 
114
class ForkedFake:
 
115
 
 
116
    def __init__(self, port, messages=None):
183
117
        self.pid = None
184
118
        self.port = port
185
119
        if messages is None:
187
121
        else:
188
122
            self.messages = messages
189
123
        self.read_end, self.write_end = os.pipe()
190
 
        self.write_logs = write_logs
191
124
 
192
125
    @staticmethod
193
126
    def from_client(client, messages=None):
194
 
        """Instantiate a ForkedFakeService from the client.
195
 
 
196
 
        :param port: The client to provide service for.
197
 
        :param messages: A dict of lists of dicts representing messages.  The
198
 
            outer dict represents the archive, the list represents the list of
199
 
            messages for that archive.
200
 
        """
201
 
        return ForkedFakeService(client.port, messages)
 
127
        return ForkedFake(client.port, messages)
202
128
 
203
129
    def is_ready(self):
204
 
        """Tell the parent process that the server is ready for writes."""
205
130
        os.write(self.write_end, 'asdf')
206
131
 
207
132
    def __enter__(self):
208
 
        """Run the service.
209
 
 
210
 
        Fork and start a server in the child.  Return when the server is ready
211
 
        for use."""
212
133
        pid = os.fork()
213
134
        if pid == 0:
214
135
            self.start_server()
217
138
        return
218
139
 
219
140
    def start_server(self):
220
 
        """Start the HTTP server."""
221
141
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
222
142
        service.store = GrackleStore(self.messages)
223
143
        for archive_id, messages in service.store.messages.iteritems():
224
144
            for message in messages:
225
145
                message.setdefault('headers', {})
226
146
        self.is_ready()
227
 
        if self.write_logs:
228
 
            logging.basicConfig(
229
 
                stream=sys.stderr, level=logging.INFO)
 
147
#        logging.basicConfig(
 
148
#            stream=sys.stderr, level=logging.INFO)
230
149
        service.serve_forever()
231
150
 
232
151
    def __exit__(self, exc_type, exc_val, traceback):
233
152
        os.kill(self.pid, SIGKILL)
234
153
 
235
154
 
236
 
SUPPORTED_DISPLAY_TYPES = set(['all', 'text-only', 'headers-only'])
237
 
 
238
 
 
239
155
SUPPORTED_ORDERS = set(
240
156
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
241
157
     'thread_subject'])
242
158
 
243
159
 
244
160
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
245
 
    """A request handler that forwards to server.store."""
246
161
 
247
162
    def __init__(self, *args, **kwargs):
248
 
        """Constructor.  Sets up logging."""
249
163
        self.logger = logging.getLogger('http')
250
164
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
251
165
 
252
166
    def do_POST(self):
253
 
        """Create a message on POST."""
254
167
        message = self.rfile.read(int(self.headers['content-length']))
255
168
        if message == 'This is a message':
256
169
            self.send_response(httplib.CREATED)
260
173
            self.send_error(httplib.BAD_REQUEST)
261
174
 
262
175
    def do_GET(self):
263
 
        """Retrieve a list of messages on GET."""
264
176
        scheme, netloc, path, params, query_string, fragments = (
265
177
            urlparse(self.path))
266
178
        parts = path.split('/')
272
184
                self.end_headers()
273
185
                self.wfile.write(simplejson.dumps(response))
274
186
            except UnsupportedOrder:
275
 
                self.send_response(
276
 
                    httplib.BAD_REQUEST, UnsupportedOrder.__doc__)
277
 
                return
278
 
            except UnsupportedDisplayType:
279
 
                self.send_response(
280
 
                    httplib.BAD_REQUEST, UnsupportedDisplayType.__doc__)
 
187
                self.send_response(httplib.BAD_REQUEST)
 
188
                self.wfile.write('Unsupported order')
281
189
                return
282
190
 
283
191
    def log_message(self, format, *args):
284
 
        """Override log_message to use standard Python logging."""
285
192
        message = "%s - - [%s] %s\n" % (
286
 
            self.address_string(), self.log_date_time_string(), format % args)
 
193
            self.address_string(), self.log_date_time_string(), format%args)
287
194
        self.logger.info(message)
288
195
 
289
196
 
291
198
 
292
199
    def test_put_message(self):
293
200
        client = GrackleClient('localhost', 8436)
294
 
        with ForkedFakeService.from_client(client):
 
201
        with ForkedFake.from_client(client):
295
202
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
296
203
            with ExpectedException(Exception, 'wtf'):
297
204
                client.put_message('arch1', 'asdf',
305
212
 
306
213
    def assertMessageIDs(self, ids, messages):
307
214
        self.assertIDOrder(
308
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
215
            sorted(ids), sorted(messages, key=lambda m:m['message_id']))
309
216
 
310
217
    def test_get_messages(self):
311
218
        client = GrackleClient('localhost', 8435)
312
 
        archive = {
313
 
            'baz': [make_message('foo'), make_message('bar')]}
314
 
        with ForkedFakeService.from_client(client, archive):
 
219
        with ForkedFake.from_client(client,
 
220
            {'baz':
 
221
            [{'message_id': 'foo'},
 
222
             {'message_id': 'bar'}]}):
315
223
            response = client.get_messages('baz')
316
224
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
317
225
            response['messages']))
320
228
 
321
229
    def test_get_messages_by_id(self):
322
230
        client = GrackleClient('localhost', 8437)
323
 
        archive = {
324
 
            'baz': [make_message('foo'), make_message('bar')]}
325
 
        with ForkedFakeService.from_client(client, archive):
 
231
        with ForkedFake.from_client(client,
 
232
            {'baz':
 
233
            [{'message_id': 'foo'},
 
234
             {'message_id': 'bar'}]}):
326
235
            response = client.get_messages('baz', message_ids=['foo'])
327
236
        message, = response['messages']
328
237
        self.assertEqual('foo', message['message_id'])
329
238
 
330
239
    def test_get_messages_batching(self):
331
240
        client = GrackleClient('localhost', 8438)
332
 
        archive = {'baz': [make_message('foo'), make_message('bar')]}
333
 
        with ForkedFakeService.from_client(client, archive):
 
241
        with ForkedFake.from_client(client,
 
242
            {'baz':
 
243
            [{'message_id': 'foo'},
 
244
             {'message_id': 'bar'}]}):
334
245
            response = client.get_messages('baz', limit=1)
335
246
            self.assertEqual(1, len(response['messages']))
336
247
            messages = response['messages']
342
253
 
343
254
    def get_messages_member_order_test(self, key):
344
255
        client = GrackleClient('localhost', 8439)
345
 
        archive = {
346
 
            'baz': [
347
 
                make_message('foo', headers={key: '2011-03-25'}),
348
 
                make_message('bar', headers={key: '2011-03-24'}),
349
 
             ]}
350
 
        with ForkedFakeService.from_client(client, archive):
 
256
        with ForkedFake.from_client(client,
 
257
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
 
258
                 {'message_id': 'bar', key: '2011-03-24'}]}):
351
259
            response = client.get_messages('baz')
352
260
            self.assertIDOrder(['foo', 'bar'], response['messages'])
353
261
            response = client.get_messages('baz', order=key)
363
271
        self.get_messages_member_order_test('subject')
364
272
 
365
273
    def test_get_messages_thread_subject_order(self):
366
 
        archive = {
367
 
            'baz': [
368
 
                make_message('bar', headers={'subject': 'y'}),
369
 
                make_message('qux', headers={'subject': 'z'}),
370
 
                make_message('foo', headers={'subject': 'x',
371
 
                                             'in-reply-to': 'qux'}),
372
 
             ]}
373
274
        client = GrackleClient('localhost', 8439)
374
 
        with ForkedFakeService.from_client(client, archive):
 
275
        with ForkedFake.from_client(client, {'baz': [
 
276
            {'message_id': 'bar', 'subject': 'y'},
 
277
            {'message_id': 'qux', 'subject': 'z'},
 
278
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
 
279
            ]}):
375
280
            response = client.get_messages('baz')
376
281
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
377
282
            response = client.get_messages('baz', order='subject')
381
286
 
382
287
    def test_get_messages_thread_oldest_order(self):
383
288
        client = GrackleClient('localhost', 8439)
384
 
        archive = {
385
 
            'baz': [
386
 
                make_message('bar', headers={'date': 'x'}),
387
 
                make_message('qux', headers={'date': 'z'}),
388
 
                make_message('foo', headers={'date': 'y',
389
 
                                             'in-reply-to': 'qux'}),
390
 
            ]}
391
 
        with ForkedFakeService.from_client(client, archive):
 
289
        with ForkedFake.from_client(client, {'baz': [
 
290
            {'message_id': 'bar', 'date': 'x'},
 
291
            {'message_id': 'qux', 'date': 'z'},
 
292
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
 
293
            ]}):
392
294
            response = client.get_messages('baz')
393
295
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
394
296
            response = client.get_messages('baz', order='date')
398
300
 
399
301
    def test_get_messages_thread_newest_order(self):
400
302
        client = GrackleClient('localhost', 8439)
401
 
        archive = {
402
 
            'baz': [
403
 
                make_message('bar', headers={'date': 'x'}),
404
 
                make_message('qux', headers={'date': 'w'}),
405
 
                make_message('foo', headers={'date': 'y',
406
 
                                             'in-reply-to': 'bar'}),
407
 
                make_message('baz', headers={'date': 'z',
408
 
                                             'in-reply-to': 'qux'}),
409
 
            ]}
410
 
        with ForkedFakeService.from_client(client, archive):
 
303
        with ForkedFake.from_client(client, {'baz': [
 
304
            {'message_id': 'bar', 'date': 'x'},
 
305
            {'message_id': 'qux', 'date': 'w'},
 
306
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
 
307
            {'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
 
308
            ]}):
411
309
            response = client.get_messages('baz', order='date')
412
310
            self.assertIDOrder(
413
311
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
417
315
 
418
316
    def test_get_messages_unsupported_order(self):
419
317
        client = GrackleClient('localhost', 8439)
420
 
        archive = {
421
 
            'baz': [
422
 
                make_message('foo', headers={'date': '2011-03-25'}),
423
 
                make_message('foo', headers={'date': '2011-03-24'}),
424
 
            ]}
425
 
        with ForkedFakeService.from_client(client, archive):
426
 
            with ExpectedException(UnsupportedOrder, ''):
 
318
        with ForkedFake.from_client(client,
 
319
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
 
320
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
 
321
            with ExpectedException(UnsupportedOrder):
427
322
                client.get_messages('baz', order='nonsense')
428
323
 
429
324
    def test_get_messages_headers_no_headers(self):
430
325
        client = GrackleClient('localhost', 8440)
431
 
        archive = {'baz': [make_message('foo')]}
432
 
        with ForkedFakeService.from_client(client, archive):
 
326
        with ForkedFake.from_client(client,
 
327
            {'baz': [
 
328
                {'message_id': 'foo'}
 
329
            ]}):
433
330
            response = client.get_messages('baz', headers=[
434
331
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
435
332
        first_message = response['messages'][0]
438
335
 
439
336
    def test_get_messages_headers_exclude_headers(self):
440
337
        client = GrackleClient('localhost', 8441)
441
 
        archive = {
442
 
            'baz': [make_message('foo', headers={'From': 'me'})]}
443
 
        with ForkedFakeService.from_client(client, archive):
 
338
        with ForkedFake.from_client(client,
 
339
            {'baz': [
 
340
                {'message_id': 'foo', 'headers': {'From': 'me'}}
 
341
            ]}):
444
342
            response = client.get_messages('baz', headers=[
445
343
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
446
344
        first_message = response['messages'][0]
449
347
 
450
348
    def test_get_messages_headers_include_headers(self):
451
349
        client = GrackleClient('localhost', 8442)
452
 
        archive = {
453
 
            'baz': [
454
 
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
455
 
        with ForkedFakeService.from_client(client, archive):
 
350
        with ForkedFake.from_client(client,
 
351
            {'baz': [
 
352
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
 
353
            ]}):
456
354
            response = client.get_messages('baz', headers=[
457
355
                'From', 'To'])
458
356
        first_message = response['messages'][0]
461
359
 
462
360
    def test_get_messages_max_body_length(self):
463
361
        client = GrackleClient('localhost', 8443)
464
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
465
 
        with ForkedFakeService.from_client(client, archive):
 
362
        with ForkedFake.from_client(client,
 
363
            {'baz': [
 
364
                {'message_id': 'foo', 'body': u'abcdefghi'}
 
365
            ]}):
466
366
            response = client.get_messages('baz', max_body_length=3)
467
367
        first_message = response['messages'][0]
468
368
        self.assertEqual('abc', first_message['body'])
469
369
 
470
370
    def test_include_hidden(self):
471
371
        client = GrackleClient('localhost', 8444)
472
 
        archive = {
473
 
            'baz': [
474
 
                make_message('foo', hidden=True),
475
 
                make_message('bar', hidden=False),
476
 
            ]}
477
 
        with ForkedFakeService.from_client(client, archive):
 
372
        with ForkedFake.from_client(client,
 
373
            {'baz': [
 
374
                {'message_id': 'foo', 'hidden': True},
 
375
                {'message_id': 'bar', 'hidden': False}
 
376
            ]}):
478
377
            response = client.get_messages('baz', include_hidden=True)
479
378
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
480
379
            response = client.get_messages('baz', include_hidden=False)
481
380
            self.assertMessageIDs(['bar'], response['messages'])
482
381
 
483
 
    def test_display_type_unknown_value(self):
484
 
        client = GrackleClient('localhost', 8445)
485
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
486
 
        with ForkedFakeService.from_client(client, archive):
487
 
            with ExpectedException(UnsupportedDisplayType, ''):
488
 
                client.get_messages('baz', display_type='unknown')
489
 
 
490
 
    def test_display_type_headers_only(self):
491
 
        client = GrackleClient('localhost', 8446)
492
 
        archive = {
493
 
            'baz': [
494
 
                make_message('foo', body=u'abcdefghi',
495
 
                             headers={'From': 'me', 'To': 'you'})]}
496
 
        with ForkedFakeService.from_client(client, archive):
497
 
            response = client.get_messages('baz', display_type='headers-only')
498
 
        first_message = response['messages'][0]
499
 
        self.assertEqual('foo', first_message['message_id'])
500
 
        self.assertEqual(
501
 
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
502
 
            first_message['headers'])
503
 
        self.assertNotIn('body', first_message)
504
 
 
505
 
    def test_display_type_text_only(self):
506
 
        client = GrackleClient('localhost', 8446)
507
 
        archive = {
508
 
            'baz': [
509
 
                make_mime_message(
510
 
                    'foo', 'abcdefghi',
511
 
                    headers={'From': 'me', 'To': 'you'},
512
 
                    attachment_type='text/x-diff')]}
513
 
        with ForkedFakeService.from_client(client, archive):
514
 
            response = client.get_messages('baz', display_type='text-only')
515
 
        first_message = response['messages'][0]
516
 
        self.assertEqual('foo', first_message['message_id'])
517
 
        self.assertEqual('me', first_message['headers']['From'])
518
 
        self.assertEqual('you', first_message['headers']['To'])
519
 
        self.assertEqual('abcdefghi', first_message['body'])
520
 
 
521
 
    def test_display_type_all(self):
522
 
        client = GrackleClient('localhost', 8447)
523
 
        archive = {
524
 
            'baz': [
525
 
                make_mime_message(
526
 
                    'foo', 'abcdefghi',
527
 
                    headers={'From': 'me', 'To': 'you'},
528
 
                    attachment_type='text/x-diff')]}
529
 
        with ForkedFakeService.from_client(client, archive):
530
 
            response = client.get_messages('baz', display_type='all')
531
 
        first_message = response['messages'][0]
532
 
        self.assertEqual('foo', first_message['message_id'])
533
 
        self.assertEqual('me', first_message['headers']['From'])
534
 
        self.assertEqual('you', first_message['headers']['To'])
535
 
        self.assertEqual(
536
 
            'abcdefghi\n\nattactment data.', first_message['body'])