~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-03-16 19:49:31 UTC
  • Revision ID: curtis.hovey@canonical.com-20120316194931-xngt1fdw9ewovwqn
Use PUT for creating messages.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from BaseHTTPServer import (
 
2
    HTTPServer,
 
3
    BaseHTTPRequestHandler,
 
4
    )
 
5
from email.message import Message
 
6
from email.mime.multipart import MIMEMultipart
 
7
from email.mime.text import MIMEText
 
8
import httplib
 
9
import logging
 
10
import os
 
11
from signal import SIGKILL
 
12
import simplejson
 
13
from StringIO import StringIO
 
14
import sys
 
15
from unittest import TestCase
 
16
from urlparse import urlparse
 
17
 
 
18
from testtools import ExpectedException
 
19
 
 
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
    message_headers = {
 
36
        'Message-Id': message_id,
 
37
        'date': '2005-01-01',
 
38
        'subject': 'subject',
 
39
        'from': 'author',
 
40
        'replies': '',
 
41
        }
 
42
    message_headers.update(headers.items())
 
43
    message = Message()
 
44
    message.set_payload(body)
 
45
    for key, value in message_headers.items():
 
46
        message[key] = value
 
47
    return make_json_message(message_id, message.as_string(), hidden)
 
48
 
 
49
 
 
50
def make_mime_message(message_id, body='body', headers=None, hidden=False,
 
51
                      attachment_type=None):
 
52
    parts = MIMEMultipart()
 
53
    parts.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
        parts.attach(attachment)
 
60
    return make_message(message_id, parts.as_string(), 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
        """
 
75
        self.pid = None
 
76
        self.port = port
 
77
        if message_archives is None:
 
78
            self.message_archives = {}
 
79
        else:
 
80
            self.message_archives = message_archives
 
81
        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
 
 
95
    def is_ready(self):
 
96
        """Tell the parent process that the server is ready for writes."""
 
97
        os.write(self.write_end, 'asdf')
 
98
 
 
99
    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
        pid = os.fork()
 
105
        if pid == 0:
 
106
            self.start_server()
 
107
        self.pid = pid
 
108
        os.read(self.read_end, 1)
 
109
        return
 
110
 
 
111
    def start_server(self):
 
112
        """Start the HTTP server."""
 
113
        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', {})
 
118
        self.is_ready()
 
119
        if self.write_logs:
 
120
            logging.basicConfig(
 
121
                stream=sys.stderr, level=logging.INFO)
 
122
        service.serve_forever()
 
123
 
 
124
    def __exit__(self, exc_type, exc_val, traceback):
 
125
        os.kill(self.pid, SIGKILL)
 
126
 
 
127
 
 
128
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
 
 
136
    def do_PUT(self):
 
137
        """Create an archive or message on PUT."""
 
138
        scheme, netloc, path, params, query_string, fragments = (
 
139
            urlparse(self.path))
 
140
        parts = path.split('/')
 
141
        if parts[1] != 'archive':
 
142
            # This is an unknonwn operation?
 
143
            return
 
144
        if len(parts) == 4:
 
145
            # This expected path is /archive/archive_id/message_id.
 
146
            try:
 
147
                message = self.rfile.read(int(self.headers['content-length']))
 
148
                self.server.store.put_message(parts[2], parts[3], message)
 
149
                self.send_response(httplib.CREATED)
 
150
                self.end_headers()
 
151
                self.wfile.close()
 
152
            except:
 
153
                self.send_error(httplib.BAD_REQUEST)
 
154
 
 
155
    def do_POST(self):
 
156
        """Change a message on POST."""
 
157
        scheme, netloc, path, params, query_string, fragments = (
 
158
            urlparse(self.path))
 
159
        parts = path.split('/')
 
160
        if parts[1] != 'archive':
 
161
            # This is an unknonwn operation?
 
162
            return
 
163
        if len(parts) == 4:
 
164
            # This expected path is /archive/archive_id/message_id.
 
165
            try:
 
166
                # This expected path is /archive/archive_id/message_id.
 
167
                response = self.server.store.hide_message(
 
168
                    parts[2], parts[3], query_string)
 
169
                self.send_response(httplib.OK)
 
170
                self.end_headers()
 
171
                self.wfile.write(simplejson.dumps(response))
 
172
            except:
 
173
                self.send_error(httplib.BAD_REQUEST)
 
174
 
 
175
    def do_GET(self):
 
176
        """Retrieve a list of messages on GET."""
 
177
        scheme, netloc, path, params, query_string, fragments = (
 
178
            urlparse(self.path))
 
179
        parts = path.split('/')
 
180
        if parts[1] == 'archive':
 
181
            try:
 
182
                response = self.server.store.get_messages(
 
183
                    parts[2], query_string)
 
184
                self.send_response(httplib.OK)
 
185
                self.end_headers()
 
186
                self.wfile.write(simplejson.dumps(response))
 
187
            except Exception, error:
 
188
                self.send_response(
 
189
                    httplib.BAD_REQUEST, error.__doc__)
 
190
                return
 
191
 
 
192
    def log_message(self, format, *args):
 
193
        """Override log_message to use standard Python logging."""
 
194
        message = "%s - - [%s] %s\n" % (
 
195
            self.address_string(), self.log_date_time_string(), format % args)
 
196
        self.logger.info(message)
 
197
 
 
198
 
 
199
class TestPutMessage(TestCase):
 
200
 
 
201
    def test_put_message(self):
 
202
        client = GrackleClient('localhost', 8420)
 
203
        message_archives = {'arch1': []}
 
204
        with ForkedFakeService.from_client(client, message_archives):
 
205
            client.put_message('arch1', 'id1', StringIO('This is a message'))
 
206
            response = client.get_messages('arch1')
 
207
        self.assertEqual(1, len(response['messages']))
 
208
        message = response['messages'][0]
 
209
        self.assertEqual('id1', message['message_id'])
 
210
 
 
211
    def test_put_message_without_archive(self):
 
212
        client = GrackleClient('localhost', 8421)
 
213
        message_archives = {'arch1': []}
 
214
        with ForkedFakeService.from_client(client, message_archives):
 
215
            with ExpectedException(Exception, 'wtf'):
 
216
                client.put_message('no-archive', 'id1', StringIO('message'))
 
217
 
 
218
 
 
219
class TestGetMessages(TestCase):
 
220
 
 
221
    def assertIDOrder(self, ids, messages):
 
222
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
223
 
 
224
    def assertMessageIDs(self, ids, messages):
 
225
        self.assertIDOrder(
 
226
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
227
 
 
228
    def test_get_messages(self):
 
229
        client = GrackleClient('localhost', 8430)
 
230
        archive = {
 
231
            'baz': [make_message('foo'), make_message('bar')]}
 
232
        with ForkedFakeService.from_client(client, archive):
 
233
            response = client.get_messages('baz')
 
234
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
 
235
            response['messages']))
 
236
        self.assertIs(None, response['next_memo'])
 
237
        self.assertIs(None, response['previous_memo'])
 
238
 
 
239
    def test_get_messages_by_id(self):
 
240
        client = GrackleClient('localhost', 8437)
 
241
        archive = {
 
242
            'baz': [make_message('foo'), make_message('bar')]}
 
243
        with ForkedFakeService.from_client(client, archive):
 
244
            response = client.get_messages('baz', message_ids=['foo'])
 
245
        message, = response['messages']
 
246
        self.assertEqual('foo', message['message_id'])
 
247
 
 
248
    def test_get_messages_batching(self):
 
249
        client = GrackleClient('localhost', 8438)
 
250
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
251
        with ForkedFakeService.from_client(client, archive):
 
252
            response = client.get_messages('baz', limit=1)
 
253
            self.assertEqual(1, len(response['messages']))
 
254
            messages = response['messages']
 
255
            response = client.get_messages(
 
256
                'baz', limit=1, memo=response['next_memo'])
 
257
            self.assertEqual(1, len(response['messages']))
 
258
            messages.extend(response['messages'])
 
259
            self.assertMessageIDs(['foo', 'bar'], messages)
 
260
 
 
261
    def get_messages_member_order_test(self, key):
 
262
        client = GrackleClient('localhost', 8439)
 
263
        if key == 'author':
 
264
            header_name = 'from'
 
265
        else:
 
266
            header_name = key
 
267
        archive = {
 
268
            'baz': [
 
269
                make_message('foo', headers={header_name: '2011-03-25'}),
 
270
                make_message('bar', headers={header_name: '2011-03-24'}),
 
271
             ]}
 
272
        with ForkedFakeService.from_client(client, archive):
 
273
            response = client.get_messages('baz')
 
274
            self.assertIDOrder(['foo', 'bar'], response['messages'])
 
275
            response = client.get_messages('baz', order=key)
 
276
            self.assertIDOrder(['bar', 'foo'], response['messages'])
 
277
 
 
278
    def test_get_messages_date_order(self):
 
279
        self.get_messages_member_order_test('date')
 
280
 
 
281
    def test_get_messages_author_order(self):
 
282
        self.get_messages_member_order_test('author')
 
283
 
 
284
    def test_get_messages_subject_order(self):
 
285
        self.get_messages_member_order_test('subject')
 
286
 
 
287
    def test_get_messages_thread_subject_order(self):
 
288
        archive = {
 
289
            'baz': [
 
290
                make_message('bar', headers={'subject': 'y'}),
 
291
                make_message('qux', headers={'subject': 'z'}),
 
292
                make_message('foo', headers={'subject': 'x',
 
293
                                             'in-reply-to': 'qux'}),
 
294
             ]}
 
295
        client = GrackleClient('localhost', 8439)
 
296
        with ForkedFakeService.from_client(client, archive):
 
297
            response = client.get_messages('baz')
 
298
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
299
            response = client.get_messages('baz', order='subject')
 
300
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
 
301
            response = client.get_messages('baz', order='thread_subject')
 
302
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
303
 
 
304
    def test_get_messages_thread_oldest_order(self):
 
305
        client = GrackleClient('localhost', 8439)
 
306
        archive = {
 
307
            'baz': [
 
308
                make_message('bar', headers={'date': 'x'}),
 
309
                make_message('qux', headers={'date': 'z'}),
 
310
                make_message('foo', headers={'date': 'y',
 
311
                                             'in-reply-to': 'qux'}),
 
312
            ]}
 
313
        with ForkedFakeService.from_client(client, archive):
 
314
            response = client.get_messages('baz')
 
315
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
316
            response = client.get_messages('baz', order='date')
 
317
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
 
318
            response = client.get_messages('baz', order='thread_oldest')
 
319
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
320
 
 
321
    def test_get_messages_thread_newest_order(self):
 
322
        client = GrackleClient('localhost', 8439)
 
323
        archive = {
 
324
            'baz': [
 
325
                make_message('bar', headers={'date': 'x'}),
 
326
                make_message('qux', headers={'date': 'w'}),
 
327
                make_message('foo', headers={'date': 'y',
 
328
                                             'in-reply-to': 'bar'}),
 
329
                make_message('baz', headers={'date': 'z',
 
330
                                             'in-reply-to': 'qux'}),
 
331
            ]}
 
332
        with ForkedFakeService.from_client(client, archive):
 
333
            response = client.get_messages('baz', order='date')
 
334
            self.assertIDOrder(
 
335
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
336
            response = client.get_messages('baz', order='thread_newest')
 
337
            self.assertIDOrder(
 
338
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
339
 
 
340
    def test_get_messages_unsupported_order(self):
 
341
        client = GrackleClient('localhost', 8439)
 
342
        archive = {
 
343
            'baz': [
 
344
                make_message('foo', headers={'date': '2011-03-25'}),
 
345
                make_message('foo', headers={'date': '2011-03-24'}),
 
346
            ]}
 
347
        with ForkedFakeService.from_client(client, archive):
 
348
            with ExpectedException(UnsupportedOrder, ''):
 
349
                client.get_messages('baz', order='nonsense')
 
350
 
 
351
    def test_get_messages_headers_no_headers(self):
 
352
        client = GrackleClient('localhost', 8440)
 
353
        archive = {'baz': [make_message('foo')]}
 
354
        with ForkedFakeService.from_client(client, archive):
 
355
            response = client.get_messages('baz', headers=[
 
356
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
357
        first_message = response['messages'][0]
 
358
        self.assertEqual('foo', first_message['message_id'])
 
359
        self.assertEqual({}, first_message['headers'])
 
360
 
 
361
    def test_get_messages_headers_exclude_headers(self):
 
362
        client = GrackleClient('localhost', 8441)
 
363
        archive = {
 
364
            'baz': [make_message('foo', headers={'From': 'me'})]}
 
365
        with ForkedFakeService.from_client(client, archive):
 
366
            response = client.get_messages('baz', headers=[
 
367
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
368
        first_message = response['messages'][0]
 
369
        self.assertEqual('foo', first_message['message_id'])
 
370
        self.assertEqual({}, first_message['headers'])
 
371
 
 
372
    def test_get_messages_headers_include_headers(self):
 
373
        client = GrackleClient('localhost', 8442)
 
374
        archive = {
 
375
            'baz': [
 
376
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
 
377
        with ForkedFakeService.from_client(client, archive):
 
378
            response = client.get_messages('baz', headers=[
 
379
                'From', 'To'])
 
380
        first_message = response['messages'][0]
 
381
        self.assertEqual('foo', first_message['message_id'])
 
382
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
383
 
 
384
    def test_get_messages_max_body_length(self):
 
385
        client = GrackleClient('localhost', 8443)
 
386
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
387
        with ForkedFakeService.from_client(client, archive):
 
388
            response = client.get_messages('baz', max_body_length=3)
 
389
        first_message = response['messages'][0]
 
390
        self.assertEqual('abc', first_message['body'])
 
391
 
 
392
    def test_include_hidden(self):
 
393
        client = GrackleClient('localhost', 8444)
 
394
        archive = {
 
395
            'baz': [
 
396
                make_message('foo', hidden=True),
 
397
                make_message('bar', hidden=False),
 
398
            ]}
 
399
        with ForkedFakeService.from_client(client, archive):
 
400
            response = client.get_messages('baz', include_hidden=True)
 
401
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
 
402
            response = client.get_messages('baz', include_hidden=False)
 
403
            self.assertMessageIDs(['bar'], response['messages'])
 
404
 
 
405
    def test_display_type_unknown_value(self):
 
406
        client = GrackleClient('localhost', 8445)
 
407
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
408
        with ForkedFakeService.from_client(client, archive):
 
409
            with ExpectedException(UnsupportedDisplayType, ''):
 
410
                client.get_messages('baz', display_type='unknown')
 
411
 
 
412
    def test_display_type_headers_only(self):
 
413
        client = GrackleClient('localhost', 8446)
 
414
        archive = {
 
415
            'baz': [
 
416
                make_message('foo', body=u'abcdefghi',
 
417
                             headers={'From': 'me', 'To': 'you'})]}
 
418
        with ForkedFakeService.from_client(client, archive):
 
419
            response = client.get_messages('baz', display_type='headers-only')
 
420
        first_message = response['messages'][0]
 
421
        self.assertEqual('foo', first_message['message_id'])
 
422
        self.assertEqual(
 
423
            archive['baz'][0]['headers'], first_message['headers'])
 
424
        self.assertNotIn('body', first_message)
 
425
 
 
426
    def test_display_type_text_only(self):
 
427
        client = GrackleClient('localhost', 8446)
 
428
        archive = {
 
429
            'baz': [
 
430
                make_mime_message(
 
431
                    'foo', 'abcdefghi',
 
432
                    headers={'From': 'me', 'To': 'you'},
 
433
                    attachment_type='text/x-diff')]}
 
434
        with ForkedFakeService.from_client(client, archive):
 
435
            response = client.get_messages('baz', display_type='text-only')
 
436
        first_message = response['messages'][0]
 
437
        self.assertEqual('foo', first_message['message_id'])
 
438
        self.assertEqual('me', first_message['headers']['From'])
 
439
        self.assertEqual('you', first_message['headers']['To'])
 
440
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
441
 
 
442
    def test_display_type_all(self):
 
443
        client = GrackleClient('localhost', 8447)
 
444
        archive = {
 
445
            'baz': [
 
446
                make_mime_message(
 
447
                    'foo', 'abcdefghi',
 
448
                    headers={'From': 'me', 'To': 'you'},
 
449
                    attachment_type='text/x-diff')]}
 
450
        with ForkedFakeService.from_client(client, archive):
 
451
            response = client.get_messages('baz', display_type='all')
 
452
        first_message = response['messages'][0]
 
453
        self.assertEqual('foo', first_message['message_id'])
 
454
        self.assertEqual('me', first_message['headers']['From'])
 
455
        self.assertEqual('you', first_message['headers']['To'])
 
456
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
457
 
 
458
    def test_date_range(self):
 
459
        client = GrackleClient('localhost', 8448)
 
460
        archive = {
 
461
            'baz': [
 
462
                make_mime_message(
 
463
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
 
464
                make_mime_message(
 
465
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
 
466
                make_mime_message(
 
467
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
 
468
                make_mime_message(
 
469
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
 
470
                make_mime_message(
 
471
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
 
472
                    ]}
 
473
        with ForkedFakeService.from_client(client, archive):
 
474
            response = client.get_messages(
 
475
                'baz', date_range='2012-01-01..2012-01-31')
 
476
        ids = sorted(m['message_id'] for m in response['messages'])
 
477
        self.assertEqual(['bar', 'naf', 'qux'], ids)
 
478
 
 
479
    def test_date_range_unparsabledaterange(self):
 
480
        client = GrackleClient('localhost', 8449)
 
481
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
482
        with ForkedFakeService.from_client(client, archive):
 
483
            with ExpectedException(UnparsableDateRange, ''):
 
484
                client.get_messages('baz', date_range='2012-01-01')
 
485
 
 
486
    def test_date_range_unparsabledaterange_missing_part(self):
 
487
        client = GrackleClient('localhost', 8450)
 
488
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
489
        with ForkedFakeService.from_client(client, archive):
 
490
            with ExpectedException(UnparsableDateRange, ''):
 
491
                client.get_messages('baz', date_range='2012-01-01..')
 
492
 
 
493
    def test_date_range_unparsabledaterange_extra_part(self):
 
494
        client = GrackleClient('localhost', 8451)
 
495
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
496
        with ForkedFakeService.from_client(client, archive):
 
497
            with ExpectedException(UnparsableDateRange, ''):
 
498
                client.get_messages('baz', date_range='2012-01..12-02..12-03')
 
499
 
 
500
 
 
501
class TestHideMessages(TestCase):
 
502
 
 
503
    def test_hide_message_true(self):
 
504
        client = GrackleClient('localhost', 8470)
 
505
        archive = {
 
506
            'baz': [
 
507
                make_message('foo', hidden=False),
 
508
            ]}
 
509
        with ForkedFakeService.from_client(client, archive):
 
510
            response = client.hide_message('baz', 'foo', hidden=True)
 
511
        self.assertEqual('foo', response['message_id'])
 
512
        self.assertIs(True, response['hidden'])
 
513
 
 
514
    def test_hide_message_false(self):
 
515
        client = GrackleClient('localhost', 8470)
 
516
        archive = {
 
517
            'baz': [
 
518
                make_message('foo', hidden=True),
 
519
            ]}
 
520
        with ForkedFakeService.from_client(client, archive):
 
521
            response = client.hide_message('baz', 'foo', hidden=False)
 
522
        self.assertEqual('foo', response['message_id'])
 
523
        self.assertIs(False, response['hidden'])