~unity-2d-team/unity-2d/Shell-MultiMonitor

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: William Grant
  • Date: 2012-01-22 06:41:06 UTC
  • Revision ID: william.grant@canonical.com-20120122064106-6at8ool919624m0z
Let the HTTP client decide count and order (to an extent).

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
 
import simplejson
13
8
from StringIO import StringIO
14
 
import sys
15
9
from unittest import TestCase
16
 
from urlparse import urlparse
17
10
 
18
11
from testtools import ExpectedException
19
12
 
20
 
from grackle.client import (
21
 
    GrackleClient,
22
 
    UnparsableDateRange,
23
 
    UnsupportedDisplayType,
24
 
    UnsupportedOrder,
25
 
    )
26
 
from grackle.store import (
27
 
    make_json_message,
28
 
    MemoryStore,
29
 
    )
30
 
 
31
 
 
32
 
def make_message(message_id, body='body', headers=None, hidden=False):
33
 
    if headers is None:
34
 
        headers = {}
35
 
    headers['Message-Id'] = message_id
36
 
    message = {
37
 
        'message_id': message_id,
38
 
        'headers': headers,
39
 
        'thread_id': message_id,
40
 
        'date': headers.get('date', '2005-01-01'),
41
 
        'subject': headers.get('subject', 'subject'),
42
 
        'author': headers.get('author', 'author'),
43
 
        'hidden': hidden,
44
 
        'attachments': [],
45
 
        'replies': headers.get('in-reply-to', None),
46
 
        'body': body,
47
 
        }
48
 
    return message
49
 
 
50
 
 
51
 
def make_mime_message(message_id, body='body', headers=None, hidden=False,
52
 
                      attachment_type=None):
53
 
    message = MIMEMultipart()
54
 
    message.attach(MIMEText(body))
55
 
    if headers is None:
56
 
        headers = {}
57
 
    for key, value in headers.items():
58
 
        message[key] = value
59
 
    if attachment_type is not None:
60
 
        attachment = Message()
61
 
        attachment.set_payload('attactment data.')
62
 
        attachment['Content-Type'] = attachment_type
63
 
        attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
64
 
        message.attach(attachment)
65
 
    return make_json_message(message_id, message.as_string())
66
 
 
67
 
 
68
 
class ForkedFakeService:
69
 
    """A Grackle service fake, as a ContextManager."""
70
 
 
71
 
    def __init__(self, port, message_archives=None, write_logs=False):
72
 
        """Constructor.
73
 
 
74
 
        :param port: The tcp port to use.
75
 
        :param message_archives: A dict of lists of dicts representing
76
 
            archives of messages. The outer dict represents the archive,
77
 
            the list represents the list of messages for that archive.
78
 
        :param write_logs: If true, log messages will be written to stdout.
79
 
        """
 
13
from grackle import client
 
14
 
 
15
 
 
16
class Forked:
 
17
 
 
18
    def __init__(self, func_or_method):
 
19
        self.func_or_method = func_or_method
80
20
        self.pid = None
81
 
        self.port = port
82
 
        if message_archives is None:
83
 
            self.message_archives = {}
84
 
        else:
85
 
            self.message_archives = message_archives
86
 
        self.read_end, self.write_end = os.pipe()
87
 
        self.write_logs = write_logs
88
 
 
89
 
    @staticmethod
90
 
    def from_client(client, message_archives=None):
91
 
        """Instantiate a ForkedFakeService from the client.
92
 
 
93
 
        :param port: The client to provide service for.
94
 
        :param message_archives: A dict of lists of dicts representing
95
 
            archives of messages. The outer dict represents the archive,
96
 
            the list represents the list of messages for that archive.
97
 
        """
98
 
        return ForkedFakeService(client.port, message_archives)
99
 
 
100
 
    def is_ready(self):
101
 
        """Tell the parent process that the server is ready for writes."""
102
 
        os.write(self.write_end, 'asdf')
103
21
 
104
22
    def __enter__(self):
105
 
        """Run the service.
106
 
 
107
 
        Fork and start a server in the child.  Return when the server is ready
108
 
        for use."""
109
23
        pid = os.fork()
110
 
        if pid == 0:
111
 
            self.start_server()
112
 
        self.pid = pid
113
 
        os.read(self.read_end, 1)
114
 
        return
 
24
        if pid != 0:
 
25
            self.pid = pid
 
26
            return
 
27
        self.func_or_method()
115
28
 
116
 
    def start_server(self):
117
 
        """Start the HTTP server."""
118
 
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
119
 
        service.store = MemoryStore(self.message_archives)
120
 
        for archive_id, messages in service.store.message_archives.iteritems():
121
 
            for message in messages:
122
 
                message.setdefault('headers', {})
123
 
        self.is_ready()
124
 
        if self.write_logs:
125
 
            logging.basicConfig(
126
 
                stream=sys.stderr, level=logging.INFO)
127
 
        service.serve_forever()
128
29
 
129
30
    def __exit__(self, exc_type, exc_val, traceback):
130
31
        os.kill(self.pid, SIGKILL)
131
32
 
132
33
 
133
34
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
134
 
    """A request handler that forwards to server.store."""
135
 
 
136
 
    def __init__(self, *args, **kwargs):
137
 
        """Constructor.  Sets up logging."""
138
 
        self.logger = logging.getLogger('http')
139
 
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
140
35
 
141
36
    def do_POST(self):
142
 
        """Create a message on POST."""
143
37
        message = self.rfile.read(int(self.headers['content-length']))
144
 
        scheme, netloc, path, params, query_string, fragments = (
145
 
            urlparse(self.path))
146
 
        parts = path.split('/')
147
 
        if parts[1] == 'archive' and len(parts) == 4:
148
 
            try:
149
 
                # This expected path is /archive/archive_id/message_id.
150
 
                self.server.store.put_message(parts[2], parts[3], message)
151
 
                self.send_response(httplib.CREATED)
152
 
                self.end_headers()
153
 
                self.wfile.close()
154
 
            except:
155
 
                self.send_error(httplib.BAD_REQUEST)
156
 
 
157
 
    def do_GET(self):
158
 
        """Retrieve a list of messages on GET."""
159
 
        scheme, netloc, path, params, query_string, fragments = (
160
 
            urlparse(self.path))
161
 
        parts = path.split('/')
162
 
        if parts[1] == 'archive':
163
 
            try:
164
 
                response = self.server.store.get_messages(
165
 
                    parts[2], query_string)
166
 
                self.send_response(httplib.OK)
167
 
                self.end_headers()
168
 
                self.wfile.write(simplejson.dumps(response))
169
 
            except Exception, error:
170
 
                self.send_response(
171
 
                    httplib.BAD_REQUEST, error.__doc__)
172
 
                return
173
 
 
174
 
    def log_message(self, format, *args):
175
 
        """Override log_message to use standard Python logging."""
176
 
        message = "%s - - [%s] %s\n" % (
177
 
            self.address_string(), self.log_date_time_string(), format % args)
178
 
        self.logger.info(message)
 
38
        if message == 'This is a message':
 
39
            self.send_response(httplib.CREATED)
 
40
            self.end_headers()
 
41
            self.wfile.close()
 
42
        else:
 
43
            self.send_error(httplib.BAD_REQUEST)
 
44
 
 
45
 
 
46
def run_service():
 
47
    service = HTTPServer(('', 8435), FakeGrackleRequestHandler)
 
48
    service.serve_forever()
 
49
 
179
50
 
180
51
 
181
52
class TestPutMessage(TestCase):
182
53
 
183
54
    def test_put_message(self):
184
 
        client = GrackleClient('localhost', 8420)
185
 
        message_archives = {'arch1': []}
186
 
        with ForkedFakeService.from_client(client, message_archives):
187
 
            client.put_message('arch1', 'id1', StringIO('This is a message'))
188
 
            response = client.get_messages('arch1')
189
 
        self.assertEqual(1, len(response['messages']))
190
 
        message = response['messages'][0]
191
 
        self.assertEqual('id1', message['message_id'])
192
 
 
193
 
    def test_put_message_without_archive(self):
194
 
        client = GrackleClient('localhost', 8421)
195
 
        message_archives = {'arch1': []}
196
 
        with ForkedFakeService.from_client(client, message_archives):
 
55
        with Forked(run_service):
 
56
            client.put_message('arch1', StringIO('This is a message'))
197
57
            with ExpectedException(Exception, 'wtf'):
198
 
                client.put_message('no-archive', 'id1', StringIO('message'))
199
 
 
200
 
 
201
 
class TestGetMessages(TestCase):
202
 
 
203
 
    def assertIDOrder(self, ids, messages):
204
 
        self.assertEqual(ids, [m['message_id'] for m in messages])
205
 
 
206
 
    def assertMessageIDs(self, ids, messages):
207
 
        self.assertIDOrder(
208
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
209
 
 
210
 
    def test_get_messages(self):
211
 
        client = GrackleClient('localhost', 8430)
212
 
        archive = {
213
 
            'baz': [make_message('foo'), make_message('bar')]}
214
 
        with ForkedFakeService.from_client(client, archive):
215
 
            response = client.get_messages('baz')
216
 
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
217
 
            response['messages']))
218
 
        self.assertIs(None, response['next_memo'])
219
 
        self.assertIs(None, response['previous_memo'])
220
 
 
221
 
    def test_get_messages_by_id(self):
222
 
        client = GrackleClient('localhost', 8437)
223
 
        archive = {
224
 
            'baz': [make_message('foo'), make_message('bar')]}
225
 
        with ForkedFakeService.from_client(client, archive):
226
 
            response = client.get_messages('baz', message_ids=['foo'])
227
 
        message, = response['messages']
228
 
        self.assertEqual('foo', message['message_id'])
229
 
 
230
 
    def test_get_messages_batching(self):
231
 
        client = GrackleClient('localhost', 8438)
232
 
        archive = {'baz': [make_message('foo'), make_message('bar')]}
233
 
        with ForkedFakeService.from_client(client, archive):
234
 
            response = client.get_messages('baz', limit=1)
235
 
            self.assertEqual(1, len(response['messages']))
236
 
            messages = response['messages']
237
 
            response = client.get_messages(
238
 
                'baz', limit=1, memo=response['next_memo'])
239
 
            self.assertEqual(1, len(response['messages']))
240
 
            messages.extend(response['messages'])
241
 
            self.assertMessageIDs(['foo', 'bar'], messages)
242
 
 
243
 
    def get_messages_member_order_test(self, key):
244
 
        client = GrackleClient('localhost', 8439)
245
 
        archive = {
246
 
            'baz': [
247
 
                make_message('foo', headers={key: '2011-03-25'}),
248
 
                make_message('bar', headers={key: '2011-03-24'}),
249
 
             ]}
250
 
        with ForkedFakeService.from_client(client, archive):
251
 
            response = client.get_messages('baz')
252
 
            self.assertIDOrder(['foo', 'bar'], response['messages'])
253
 
            response = client.get_messages('baz', order=key)
254
 
            self.assertIDOrder(['bar', 'foo'], response['messages'])
255
 
 
256
 
    def test_get_messages_date_order(self):
257
 
        self.get_messages_member_order_test('date')
258
 
 
259
 
    def test_get_messages_author_order(self):
260
 
        self.get_messages_member_order_test('author')
261
 
 
262
 
    def test_get_messages_subject_order(self):
263
 
        self.get_messages_member_order_test('subject')
264
 
 
265
 
    def test_get_messages_thread_subject_order(self):
266
 
        archive = {
267
 
            'baz': [
268
 
                make_message('bar', headers={'subject': 'y'}),
269
 
                make_message('qux', headers={'subject': 'z'}),
270
 
                make_message('foo', headers={'subject': 'x',
271
 
                                             'in-reply-to': 'qux'}),
272
 
             ]}
273
 
        client = GrackleClient('localhost', 8439)
274
 
        with ForkedFakeService.from_client(client, archive):
275
 
            response = client.get_messages('baz')
276
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
277
 
            response = client.get_messages('baz', order='subject')
278
 
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
279
 
            response = client.get_messages('baz', order='thread_subject')
280
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
281
 
 
282
 
    def test_get_messages_thread_oldest_order(self):
283
 
        client = GrackleClient('localhost', 8439)
284
 
        archive = {
285
 
            'baz': [
286
 
                make_message('bar', headers={'date': 'x'}),
287
 
                make_message('qux', headers={'date': 'z'}),
288
 
                make_message('foo', headers={'date': 'y',
289
 
                                             'in-reply-to': 'qux'}),
290
 
            ]}
291
 
        with ForkedFakeService.from_client(client, archive):
292
 
            response = client.get_messages('baz')
293
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
294
 
            response = client.get_messages('baz', order='date')
295
 
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
296
 
            response = client.get_messages('baz', order='thread_oldest')
297
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
298
 
 
299
 
    def test_get_messages_thread_newest_order(self):
300
 
        client = GrackleClient('localhost', 8439)
301
 
        archive = {
302
 
            'baz': [
303
 
                make_message('bar', headers={'date': 'x'}),
304
 
                make_message('qux', headers={'date': 'w'}),
305
 
                make_message('foo', headers={'date': 'y',
306
 
                                             'in-reply-to': 'bar'}),
307
 
                make_message('baz', headers={'date': 'z',
308
 
                                             'in-reply-to': 'qux'}),
309
 
            ]}
310
 
        with ForkedFakeService.from_client(client, archive):
311
 
            response = client.get_messages('baz', order='date')
312
 
            self.assertIDOrder(
313
 
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
314
 
            response = client.get_messages('baz', order='thread_newest')
315
 
            self.assertIDOrder(
316
 
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
317
 
 
318
 
    def test_get_messages_unsupported_order(self):
319
 
        client = GrackleClient('localhost', 8439)
320
 
        archive = {
321
 
            'baz': [
322
 
                make_message('foo', headers={'date': '2011-03-25'}),
323
 
                make_message('foo', headers={'date': '2011-03-24'}),
324
 
            ]}
325
 
        with ForkedFakeService.from_client(client, archive):
326
 
            with ExpectedException(UnsupportedOrder, ''):
327
 
                client.get_messages('baz', order='nonsense')
328
 
 
329
 
    def test_get_messages_headers_no_headers(self):
330
 
        client = GrackleClient('localhost', 8440)
331
 
        archive = {'baz': [make_message('foo')]}
332
 
        with ForkedFakeService.from_client(client, archive):
333
 
            response = client.get_messages('baz', headers=[
334
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
335
 
        first_message = response['messages'][0]
336
 
        self.assertEqual('foo', first_message['message_id'])
337
 
        self.assertEqual({}, first_message['headers'])
338
 
 
339
 
    def test_get_messages_headers_exclude_headers(self):
340
 
        client = GrackleClient('localhost', 8441)
341
 
        archive = {
342
 
            'baz': [make_message('foo', headers={'From': 'me'})]}
343
 
        with ForkedFakeService.from_client(client, archive):
344
 
            response = client.get_messages('baz', headers=[
345
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
346
 
        first_message = response['messages'][0]
347
 
        self.assertEqual('foo', first_message['message_id'])
348
 
        self.assertEqual({}, first_message['headers'])
349
 
 
350
 
    def test_get_messages_headers_include_headers(self):
351
 
        client = GrackleClient('localhost', 8442)
352
 
        archive = {
353
 
            'baz': [
354
 
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
355
 
        with ForkedFakeService.from_client(client, archive):
356
 
            response = client.get_messages('baz', headers=[
357
 
                'From', 'To'])
358
 
        first_message = response['messages'][0]
359
 
        self.assertEqual('foo', first_message['message_id'])
360
 
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
361
 
 
362
 
    def test_get_messages_max_body_length(self):
363
 
        client = GrackleClient('localhost', 8443)
364
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
365
 
        with ForkedFakeService.from_client(client, archive):
366
 
            response = client.get_messages('baz', max_body_length=3)
367
 
        first_message = response['messages'][0]
368
 
        self.assertEqual('abc', first_message['body'])
369
 
 
370
 
    def test_include_hidden(self):
371
 
        client = GrackleClient('localhost', 8444)
372
 
        archive = {
373
 
            'baz': [
374
 
                make_message('foo', hidden=True),
375
 
                make_message('bar', hidden=False),
376
 
            ]}
377
 
        with ForkedFakeService.from_client(client, archive):
378
 
            response = client.get_messages('baz', include_hidden=True)
379
 
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
380
 
            response = client.get_messages('baz', include_hidden=False)
381
 
            self.assertMessageIDs(['bar'], response['messages'])
382
 
 
383
 
    def test_display_type_unknown_value(self):
384
 
        client = GrackleClient('localhost', 8445)
385
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
386
 
        with ForkedFakeService.from_client(client, archive):
387
 
            with ExpectedException(UnsupportedDisplayType, ''):
388
 
                client.get_messages('baz', display_type='unknown')
389
 
 
390
 
    def test_display_type_headers_only(self):
391
 
        client = GrackleClient('localhost', 8446)
392
 
        archive = {
393
 
            'baz': [
394
 
                make_message('foo', body=u'abcdefghi',
395
 
                             headers={'From': 'me', 'To': 'you'})]}
396
 
        with ForkedFakeService.from_client(client, archive):
397
 
            response = client.get_messages('baz', display_type='headers-only')
398
 
        first_message = response['messages'][0]
399
 
        self.assertEqual('foo', first_message['message_id'])
400
 
        self.assertEqual(
401
 
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
402
 
            first_message['headers'])
403
 
        self.assertNotIn('body', first_message)
404
 
 
405
 
    def test_display_type_text_only(self):
406
 
        client = GrackleClient('localhost', 8446)
407
 
        archive = {
408
 
            'baz': [
409
 
                make_mime_message(
410
 
                    'foo', 'abcdefghi',
411
 
                    headers={'From': 'me', 'To': 'you'},
412
 
                    attachment_type='text/x-diff')]}
413
 
        with ForkedFakeService.from_client(client, archive):
414
 
            response = client.get_messages('baz', display_type='text-only')
415
 
        first_message = response['messages'][0]
416
 
        self.assertEqual('foo', first_message['message_id'])
417
 
        self.assertEqual('me', first_message['headers']['From'])
418
 
        self.assertEqual('you', first_message['headers']['To'])
419
 
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
420
 
 
421
 
    def test_display_type_all(self):
422
 
        client = GrackleClient('localhost', 8447)
423
 
        archive = {
424
 
            'baz': [
425
 
                make_mime_message(
426
 
                    'foo', 'abcdefghi',
427
 
                    headers={'From': 'me', 'To': 'you'},
428
 
                    attachment_type='text/x-diff')]}
429
 
        with ForkedFakeService.from_client(client, archive):
430
 
            response = client.get_messages('baz', display_type='all')
431
 
        first_message = response['messages'][0]
432
 
        self.assertEqual('foo', first_message['message_id'])
433
 
        self.assertEqual('me', first_message['headers']['From'])
434
 
        self.assertEqual('you', first_message['headers']['To'])
435
 
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
436
 
 
437
 
    def test_date_range(self):
438
 
        client = GrackleClient('localhost', 8448)
439
 
        archive = {
440
 
            'baz': [
441
 
                make_mime_message(
442
 
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
443
 
                make_mime_message(
444
 
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
445
 
                make_mime_message(
446
 
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
447
 
                make_mime_message(
448
 
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
449
 
                make_mime_message(
450
 
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
451
 
                    ]}
452
 
        with ForkedFakeService.from_client(client, archive):
453
 
            response = client.get_messages(
454
 
                'baz', date_range='2012-01-01..2012-01-31')
455
 
        ids = sorted(m['message_id'] for m in response['messages'])
456
 
        self.assertEqual(['bar', 'naf', 'qux'], ids)
457
 
 
458
 
    def test_date_range_unparsabledaterange(self):
459
 
        client = GrackleClient('localhost', 8449)
460
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
461
 
        with ForkedFakeService.from_client(client, archive):
462
 
            with ExpectedException(UnparsableDateRange, ''):
463
 
                client.get_messages('baz', date_range='2012-01-01')
464
 
 
465
 
    def test_date_range_unparsabledaterange_missing_part(self):
466
 
        client = GrackleClient('localhost', 8450)
467
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
468
 
        with ForkedFakeService.from_client(client, archive):
469
 
            with ExpectedException(UnparsableDateRange, ''):
470
 
                client.get_messages('baz', date_range='2012-01-01..')
471
 
 
472
 
    def test_date_range_unparsabledaterange_extra_part(self):
473
 
        client = GrackleClient('localhost', 8451)
474
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
475
 
        with ForkedFakeService.from_client(client, archive):
476
 
            with ExpectedException(UnparsableDateRange, ''):
477
 
                client.get_messages('baz', date_range='2012-01..12-02..12-03')
 
58
                client.put_message('arch1', StringIO('This is not a message'))