~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 08:55:46 UTC
  • Revision ID: aaron@canonical.com-20120112085546-q1bo0vats1us3nx0
archive_name -> archive_id

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, message_archives=None, write_logs=False):
67
 
        """Constructor.
68
 
 
69
 
        :param port: The tcp port to use.
70
 
        :param message_archives: A dict of lists of dicts representing
71
 
            archives of messages. The outer dict represents the archive,
72
 
            the list represents the list of 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 message_archives is None:
78
 
            self.message_archives = {}
79
 
        else:
80
 
            self.message_archives = message_archives
 
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, message_archives=None):
86
 
        """Instantiate a ForkedFakeService from the client.
87
 
 
88
 
        :param port: The client to provide service for.
89
 
        :param message_archives: A dict of lists of dicts representing
90
 
            archives of messages. The outer dict represents the archive,
91
 
            the list represents the list of messages for that archive.
92
 
        """
93
 
        return ForkedFakeService(client.port, message_archives)
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.message_archives)
115
 
        for archive_id, messages in service.store.message_archives.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
        response = {
 
73
            'messages': messages,
 
74
            'next_memo': None,
 
75
            'previous_memo': None
 
76
            }
 
77
        self.wfile.write(simplejson.dumps(response))
 
78
 
 
79
 
 
80
def fake_grackle_service(client, messages=None):
 
81
    if messages is None:
 
82
        messages = {}
 
83
    return ForkedFake(client.port, messages)
168
84
 
169
85
 
170
86
class TestPutMessage(TestCase):
171
87
 
172
88
    def test_put_message(self):
173
 
        client = GrackleClient('localhost', 8420)
174
 
        with ForkedFakeService.from_client(client):
 
89
        client = GrackleClient('localhost', 8436)
 
90
        with fake_grackle_service(client):
175
91
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
176
92
            with ExpectedException(Exception, 'wtf'):
177
93
                client.put_message('arch1', 'asdf',
180
96
 
181
97
class TestGetMessages(TestCase):
182
98
 
183
 
    def assertIDOrder(self, ids, messages):
184
 
        self.assertEqual(ids, [m['message_id'] for m in messages])
185
 
 
186
 
    def assertMessageIDs(self, ids, messages):
187
 
        self.assertIDOrder(
188
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
189
 
 
190
99
    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):
 
100
        client = GrackleClient('localhost', 8435)
 
101
        with fake_grackle_service(client,
 
102
            {'baz':
 
103
            [{'message_id': 'foo'},
 
104
             {'message_id': 'bar'}]}):
195
105
            response = client.get_messages('baz')
196
106
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
197
107
            response['messages']))
200
110
 
201
111
    def test_get_messages_by_id(self):
202
112
        client = GrackleClient('localhost', 8437)
203
 
        archive = {
204
 
            'baz': [make_message('foo'), make_message('bar')]}
205
 
        with ForkedFakeService.from_client(client, archive):
 
113
        with fake_grackle_service(client,
 
114
            {'baz':
 
115
            [{'message_id': 'foo'},
 
116
             {'message_id': 'bar'}]}):
206
117
            response = client.get_messages('baz', message_ids=['foo'])
207
118
        message, = response['messages']
208
119
        self.assertEqual('foo', message['message_id'])
209
 
 
210
 
    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):
214
 
            response = client.get_messages('baz', limit=1)
215
 
            self.assertEqual(1, len(response['messages']))
216
 
            messages = response['messages']
217
 
            response = client.get_messages(
218
 
                'baz', limit=1, memo=response['next_memo'])
219
 
            self.assertEqual(1, len(response['messages']))
220
 
            messages.extend(response['messages'])
221
 
            self.assertMessageIDs(['foo', 'bar'], messages)
222
 
 
223
 
    def get_messages_member_order_test(self, key):
224
 
        client = GrackleClient('localhost', 8439)
225
 
        archive = {
226
 
            'baz': [
227
 
                make_message('foo', headers={key: '2011-03-25'}),
228
 
                make_message('bar', headers={key: '2011-03-24'}),
229
 
             ]}
230
 
        with ForkedFakeService.from_client(client, archive):
231
 
            response = client.get_messages('baz')
232
 
            self.assertIDOrder(['foo', 'bar'], response['messages'])
233
 
            response = client.get_messages('baz', order=key)
234
 
            self.assertIDOrder(['bar', 'foo'], response['messages'])
235
 
 
236
 
    def test_get_messages_date_order(self):
237
 
        self.get_messages_member_order_test('date')
238
 
 
239
 
    def test_get_messages_author_order(self):
240
 
        self.get_messages_member_order_test('author')
241
 
 
242
 
    def test_get_messages_subject_order(self):
243
 
        self.get_messages_member_order_test('subject')
244
 
 
245
 
    def test_get_messages_thread_subject_order(self):
246
 
        archive = {
247
 
            'baz': [
248
 
                make_message('bar', headers={'subject': 'y'}),
249
 
                make_message('qux', headers={'subject': 'z'}),
250
 
                make_message('foo', headers={'subject': 'x',
251
 
                                             'in-reply-to': 'qux'}),
252
 
             ]}
253
 
        client = GrackleClient('localhost', 8439)
254
 
        with ForkedFakeService.from_client(client, archive):
255
 
            response = client.get_messages('baz')
256
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
257
 
            response = client.get_messages('baz', order='subject')
258
 
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
259
 
            response = client.get_messages('baz', order='thread_subject')
260
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
261
 
 
262
 
    def test_get_messages_thread_oldest_order(self):
263
 
        client = GrackleClient('localhost', 8439)
264
 
        archive = {
265
 
            'baz': [
266
 
                make_message('bar', headers={'date': 'x'}),
267
 
                make_message('qux', headers={'date': 'z'}),
268
 
                make_message('foo', headers={'date': 'y',
269
 
                                             'in-reply-to': 'qux'}),
270
 
            ]}
271
 
        with ForkedFakeService.from_client(client, archive):
272
 
            response = client.get_messages('baz')
273
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
274
 
            response = client.get_messages('baz', order='date')
275
 
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
276
 
            response = client.get_messages('baz', order='thread_oldest')
277
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
278
 
 
279
 
    def test_get_messages_thread_newest_order(self):
280
 
        client = GrackleClient('localhost', 8439)
281
 
        archive = {
282
 
            'baz': [
283
 
                make_message('bar', headers={'date': 'x'}),
284
 
                make_message('qux', headers={'date': 'w'}),
285
 
                make_message('foo', headers={'date': 'y',
286
 
                                             'in-reply-to': 'bar'}),
287
 
                make_message('baz', headers={'date': 'z',
288
 
                                             'in-reply-to': 'qux'}),
289
 
            ]}
290
 
        with ForkedFakeService.from_client(client, archive):
291
 
            response = client.get_messages('baz', order='date')
292
 
            self.assertIDOrder(
293
 
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
294
 
            response = client.get_messages('baz', order='thread_newest')
295
 
            self.assertIDOrder(
296
 
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
297
 
 
298
 
    def test_get_messages_unsupported_order(self):
299
 
        client = GrackleClient('localhost', 8439)
300
 
        archive = {
301
 
            'baz': [
302
 
                make_message('foo', headers={'date': '2011-03-25'}),
303
 
                make_message('foo', headers={'date': '2011-03-24'}),
304
 
            ]}
305
 
        with ForkedFakeService.from_client(client, archive):
306
 
            with ExpectedException(UnsupportedOrder, ''):
307
 
                client.get_messages('baz', order='nonsense')
308
 
 
309
 
    def test_get_messages_headers_no_headers(self):
310
 
        client = GrackleClient('localhost', 8440)
311
 
        archive = {'baz': [make_message('foo')]}
312
 
        with ForkedFakeService.from_client(client, archive):
313
 
            response = client.get_messages('baz', headers=[
314
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
315
 
        first_message = response['messages'][0]
316
 
        self.assertEqual('foo', first_message['message_id'])
317
 
        self.assertEqual({}, first_message['headers'])
318
 
 
319
 
    def test_get_messages_headers_exclude_headers(self):
320
 
        client = GrackleClient('localhost', 8441)
321
 
        archive = {
322
 
            'baz': [make_message('foo', headers={'From': 'me'})]}
323
 
        with ForkedFakeService.from_client(client, archive):
324
 
            response = client.get_messages('baz', headers=[
325
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
326
 
        first_message = response['messages'][0]
327
 
        self.assertEqual('foo', first_message['message_id'])
328
 
        self.assertEqual({}, first_message['headers'])
329
 
 
330
 
    def test_get_messages_headers_include_headers(self):
331
 
        client = GrackleClient('localhost', 8442)
332
 
        archive = {
333
 
            'baz': [
334
 
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
335
 
        with ForkedFakeService.from_client(client, archive):
336
 
            response = client.get_messages('baz', headers=[
337
 
                'From', 'To'])
338
 
        first_message = response['messages'][0]
339
 
        self.assertEqual('foo', first_message['message_id'])
340
 
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
341
 
 
342
 
    def test_get_messages_max_body_length(self):
343
 
        client = GrackleClient('localhost', 8443)
344
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
345
 
        with ForkedFakeService.from_client(client, archive):
346
 
            response = client.get_messages('baz', max_body_length=3)
347
 
        first_message = response['messages'][0]
348
 
        self.assertEqual('abc', first_message['body'])
349
 
 
350
 
    def test_include_hidden(self):
351
 
        client = GrackleClient('localhost', 8444)
352
 
        archive = {
353
 
            'baz': [
354
 
                make_message('foo', hidden=True),
355
 
                make_message('bar', hidden=False),
356
 
            ]}
357
 
        with ForkedFakeService.from_client(client, archive):
358
 
            response = client.get_messages('baz', include_hidden=True)
359
 
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
360
 
            response = client.get_messages('baz', include_hidden=False)
361
 
            self.assertMessageIDs(['bar'], response['messages'])
362
 
 
363
 
    def test_display_type_unknown_value(self):
364
 
        client = GrackleClient('localhost', 8445)
365
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
366
 
        with ForkedFakeService.from_client(client, archive):
367
 
            with ExpectedException(UnsupportedDisplayType, ''):
368
 
                client.get_messages('baz', display_type='unknown')
369
 
 
370
 
    def test_display_type_headers_only(self):
371
 
        client = GrackleClient('localhost', 8446)
372
 
        archive = {
373
 
            'baz': [
374
 
                make_message('foo', body=u'abcdefghi',
375
 
                             headers={'From': 'me', 'To': 'you'})]}
376
 
        with ForkedFakeService.from_client(client, archive):
377
 
            response = client.get_messages('baz', display_type='headers-only')
378
 
        first_message = response['messages'][0]
379
 
        self.assertEqual('foo', first_message['message_id'])
380
 
        self.assertEqual(
381
 
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
382
 
            first_message['headers'])
383
 
        self.assertNotIn('body', first_message)
384
 
 
385
 
    def test_display_type_text_only(self):
386
 
        client = GrackleClient('localhost', 8446)
387
 
        archive = {
388
 
            'baz': [
389
 
                make_mime_message(
390
 
                    'foo', 'abcdefghi',
391
 
                    headers={'From': 'me', 'To': 'you'},
392
 
                    attachment_type='text/x-diff')]}
393
 
        with ForkedFakeService.from_client(client, archive):
394
 
            response = client.get_messages('baz', display_type='text-only')
395
 
        first_message = response['messages'][0]
396
 
        self.assertEqual('foo', first_message['message_id'])
397
 
        self.assertEqual('me', first_message['headers']['From'])
398
 
        self.assertEqual('you', first_message['headers']['To'])
399
 
        self.assertEqual('abcdefghi', first_message['body'])
400
 
 
401
 
    def test_display_type_all(self):
402
 
        client = GrackleClient('localhost', 8447)
403
 
        archive = {
404
 
            'baz': [
405
 
                make_mime_message(
406
 
                    'foo', 'abcdefghi',
407
 
                    headers={'From': 'me', 'To': 'you'},
408
 
                    attachment_type='text/x-diff')]}
409
 
        with ForkedFakeService.from_client(client, archive):
410
 
            response = client.get_messages('baz', display_type='all')
411
 
        first_message = response['messages'][0]
412
 
        self.assertEqual('foo', first_message['message_id'])
413
 
        self.assertEqual('me', first_message['headers']['From'])
414
 
        self.assertEqual('you', first_message['headers']['To'])
415
 
        self.assertEqual(
416
 
            'abcdefghi\n\nattactment data.', first_message['body'])
417
 
 
418
 
    def test_date_range(self):
419
 
        client = GrackleClient('localhost', 8448)
420
 
        archive = {
421
 
            'baz': [
422
 
                make_mime_message(
423
 
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
424
 
                make_mime_message(
425
 
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
426
 
                make_mime_message(
427
 
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
428
 
                make_mime_message(
429
 
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
430
 
                make_mime_message(
431
 
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
432
 
                    ]}
433
 
        with ForkedFakeService.from_client(client, archive):
434
 
            response = client.get_messages(
435
 
                'baz', date_range='2012-01-01..2012-01-31')
436
 
        ids = sorted(m['message_id'] for m in response['messages'])
437
 
        self.assertEqual(['bar', 'naf', 'qux'], ids)
438
 
 
439
 
    def test_date_range_unparsabledaterange(self):
440
 
        client = GrackleClient('localhost', 8449)
441
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
442
 
        with ForkedFakeService.from_client(client, archive):
443
 
            with ExpectedException(UnparsableDateRange, ''):
444
 
                client.get_messages('baz', date_range='2012-01-01')
445
 
 
446
 
    def test_date_range_unparsabledaterange_missing_part(self):
447
 
        client = GrackleClient('localhost', 8450)
448
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
449
 
        with ForkedFakeService.from_client(client, archive):
450
 
            with ExpectedException(UnparsableDateRange, ''):
451
 
                client.get_messages('baz', date_range='2012-01-01..')
452
 
 
453
 
    def test_date_range_unparsabledaterange_extra_part(self):
454
 
        client = GrackleClient('localhost', 8451)
455
 
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
456
 
        with ForkedFakeService.from_client(client, archive):
457
 
            with ExpectedException(UnparsableDateRange, ''):
458
 
                client.get_messages('baz', date_range='2012-01..12-02..12-03')