~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-02-14 22:49:46 UTC
  • Revision ID: curtis.hovey@canonical.com-20120214224946-rx83gm3er2pho566
Raise UnparsableDateRange when the date cannot be parsed.

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