~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 15:12:12 UTC
  • Revision ID: curtis.hovey@canonical.com-20120316151212-w2u06qd6oxi2cvli
Make an rfc822 message with a mime payload.

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