~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-13 11:46:14 UTC
  • Revision ID: aaron@canonical.com-20120113114614-lw5t8rcn4cidlb3o
Support thread_newest threading.

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