~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 10:33:21 UTC
  • Revision ID: william.grant@canonical.com-20120122103321-79m4gov8cntyxpy2
grackle-create-instance can now create the keyspace too.

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