~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
59
 
        for archive_id, messages in service.messages.iteritems():
 
121
        service.store = MemoryStore(self.message_archives)
 
122
        for archive_id, messages in service.store.message_archives.iteritems():
60
123
            for message in messages:
61
124
                message.setdefault('headers', {})
62
125
        self.is_ready()
 
126
        if self.write_logs:
 
127
            logging.basicConfig(
 
128
                stream=sys.stderr, level=logging.INFO)
63
129
        service.serve_forever()
64
130
 
65
131
    def __exit__(self, exc_type, exc_val, traceback):
66
132
        os.kill(self.pid, SIGKILL)
67
133
 
68
134
 
69
 
SUPPORTED_ORDERS = set(
70
 
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
71
 
     'thread_subject'])
72
 
 
73
 
 
74
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)
75
142
 
76
143
    def do_POST(self):
 
144
        """Create a message on POST."""
77
145
        message = self.rfile.read(int(self.headers['content-length']))
78
 
        if message == 'This is a message':
79
 
            self.send_response(httplib.CREATED)
80
 
            self.end_headers()
81
 
            self.wfile.close()
82
 
        else:
83
 
            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)
84
158
 
85
159
    def do_GET(self):
 
160
        """Retrieve a list of messages on GET."""
86
161
        scheme, netloc, path, params, query_string, fragments = (
87
162
            urlparse(self.path))
88
163
        parts = path.split('/')
89
164
        if parts[1] == 'archive':
90
 
            self.get_messages(parts[2], query_string)
91
 
 
92
 
    def get_messages(self, archive_id, query_string):
93
 
        query = parse_qs(query_string)
94
 
        parameters = simplejson.loads(query['parameters'][0])
95
 
        order = parameters.get('order')
96
 
        messages = self.server.messages[archive_id]
97
 
        if order is not None :
98
 
            if order not in SUPPORTED_ORDERS:
99
 
                self.send_response(httplib.BAD_REQUEST)
100
 
                self.wfile.write('Unsupported order')
 
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__)
101
174
                return
102
 
            elif order.startswith('thread_'):
103
 
                threaded = threaded_messages(messages)
104
 
                messages = []
105
 
                if order == 'thread_subject':
106
 
                    threaded.sort(key=lambda t: t[0]['subject'])
107
 
                if order == 'thread_oldest':
108
 
                    threaded.sort(key=lambda t: min(m['date'] for m in t))
109
 
                if order == 'thread_newest':
110
 
                    threaded.sort(key=lambda t: max(m['date'] for m in t))
111
 
                for thread in threaded:
112
 
                    messages.extend(thread)
113
 
            else:
114
 
                messages.sort(key=lambda m: m[order])
115
 
        messages = [m for m in messages
116
 
                    if 'message_ids' not in parameters or
117
 
                    m['message_id'] in parameters['message_ids']]
118
 
        self.send_response(httplib.OK)
119
 
        self.end_headers()
120
 
        limit = parameters.get('limit', 100)
121
 
        memo = parameters.get('memo')
122
 
        message_id_indices = dict(
123
 
            (m['message_id'], idx) for idx, m in enumerate(messages))
124
 
        if memo is None:
125
 
            start = 0
126
 
        else:
127
 
            start = message_id_indices[memo.encode('rot13')]
128
 
        if start > 0:
129
 
            previous_memo = messages[start - 1]['message_id'].encode('rot13')
130
 
        else:
131
 
            previous_memo = None
132
 
        end = min(start + limit, len(messages))
133
 
        if end < len(messages):
134
 
            next_memo = messages[end]['message_id'].encode('rot13')
135
 
        else:
136
 
            next_memo = None
137
 
        messages = messages[start:end]
138
 
        new_messages = []
139
 
        for message in messages:
140
 
            message = dict(message)
141
 
            if 'headers' in parameters:
142
 
                headers = dict(
143
 
                    (k, v) for k, v in message['headers'].iteritems()
144
 
                    if k in parameters['headers'])
145
 
                message['headers'] = headers
146
 
            new_messages.append(message)
147
 
        response = {
148
 
            'messages': new_messages,
149
 
            'next_memo': next_memo,
150
 
            'previous_memo': previous_memo
151
 
            }
152
 
        self.wfile.write(simplejson.dumps(response))
153
 
 
154
 
 
155
 
def fake_grackle_service(client, messages=None):
156
 
    if messages is None:
157
 
        messages = {}
158
 
    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)
159
181
 
160
182
 
161
183
class TestPutMessage(TestCase):
162
184
 
163
185
    def test_put_message(self):
164
 
        client = GrackleClient('localhost', 8436)
165
 
        with fake_grackle_service(client):
166
 
            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):
167
199
            with ExpectedException(Exception, 'wtf'):
168
 
                client.put_message('arch1', 'asdf',
169
 
                    StringIO('This is not a message'))
 
200
                client.put_message('no-archive', 'id1', StringIO('message'))
170
201
 
171
202
 
172
203
class TestGetMessages(TestCase):
176
207
 
177
208
    def assertMessageIDs(self, ids, messages):
178
209
        self.assertIDOrder(
179
 
            sorted(ids), sorted(messages, key=lambda m:m['message_id']))
 
210
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
180
211
 
181
212
    def test_get_messages(self):
182
 
        client = GrackleClient('localhost', 8435)
183
 
        with fake_grackle_service(client,
184
 
            {'baz':
185
 
            [{'message_id': 'foo'},
186
 
             {'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):
187
217
            response = client.get_messages('baz')
188
218
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
189
219
            response['messages']))
192
222
 
193
223
    def test_get_messages_by_id(self):
194
224
        client = GrackleClient('localhost', 8437)
195
 
        with fake_grackle_service(client,
196
 
            {'baz':
197
 
            [{'message_id': 'foo'},
198
 
             {'message_id': 'bar'}]}):
 
225
        archive = {
 
226
            'baz': [make_message('foo'), make_message('bar')]}
 
227
        with ForkedFakeService.from_client(client, archive):
199
228
            response = client.get_messages('baz', message_ids=['foo'])
200
229
        message, = response['messages']
201
230
        self.assertEqual('foo', message['message_id'])
202
231
 
203
232
    def test_get_messages_batching(self):
204
233
        client = GrackleClient('localhost', 8438)
205
 
        with fake_grackle_service(client,
206
 
            {'baz':
207
 
            [{'message_id': 'foo'},
208
 
             {'message_id': 'bar'}]}):
 
234
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
235
        with ForkedFakeService.from_client(client, archive):
209
236
            response = client.get_messages('baz', limit=1)
210
237
            self.assertEqual(1, len(response['messages']))
211
238
            messages = response['messages']
217
244
 
218
245
    def get_messages_member_order_test(self, key):
219
246
        client = GrackleClient('localhost', 8439)
220
 
        with fake_grackle_service(client,
221
 
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
222
 
                 {'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):
223
253
            response = client.get_messages('baz')
224
254
            self.assertIDOrder(['foo', 'bar'], response['messages'])
225
255
            response = client.get_messages('baz', order=key)
235
265
        self.get_messages_member_order_test('subject')
236
266
 
237
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
             ]}
238
275
        client = GrackleClient('localhost', 8439)
239
 
        with fake_grackle_service(client, {'baz': [
240
 
            {'message_id': 'bar', 'subject': 'y'},
241
 
            {'message_id': 'qux', 'subject': 'z'},
242
 
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
243
 
            ]}):
 
276
        with ForkedFakeService.from_client(client, archive):
244
277
            response = client.get_messages('baz')
245
278
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
246
279
            response = client.get_messages('baz', order='subject')
250
283
 
251
284
    def test_get_messages_thread_oldest_order(self):
252
285
        client = GrackleClient('localhost', 8439)
253
 
        with fake_grackle_service(client, {'baz': [
254
 
            {'message_id': 'bar', 'date': 'x'},
255
 
            {'message_id': 'qux', 'date': 'z'},
256
 
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
257
 
            ]}):
 
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):
258
294
            response = client.get_messages('baz')
259
295
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
260
296
            response = client.get_messages('baz', order='date')
264
300
 
265
301
    def test_get_messages_thread_newest_order(self):
266
302
        client = GrackleClient('localhost', 8439)
267
 
        with fake_grackle_service(client, {'baz': [
268
 
            {'message_id': 'bar', 'date': 'x'},
269
 
            {'message_id': 'qux', 'date': 'w'},
270
 
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
271
 
            {'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
272
 
            ]}):
 
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):
273
313
            response = client.get_messages('baz', order='date')
274
314
            self.assertIDOrder(
275
315
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
279
319
 
280
320
    def test_get_messages_unsupported_order(self):
281
321
        client = GrackleClient('localhost', 8439)
282
 
        with fake_grackle_service(client,
283
 
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
284
 
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
285
 
            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, ''):
286
329
                client.get_messages('baz', order='nonsense')
287
330
 
288
331
    def test_get_messages_headers_no_headers(self):
289
332
        client = GrackleClient('localhost', 8440)
290
 
        with fake_grackle_service(client,
291
 
            {'baz': [
292
 
                {'message_id': 'foo'}
293
 
            ]}):
 
333
        archive = {'baz': [make_message('foo')]}
 
334
        with ForkedFakeService.from_client(client, archive):
294
335
            response = client.get_messages('baz', headers=[
295
336
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
296
337
        first_message = response['messages'][0]
298
339
        self.assertEqual({}, first_message['headers'])
299
340
 
300
341
    def test_get_messages_headers_exclude_headers(self):
301
 
        client = GrackleClient('localhost', 8440)
302
 
        with fake_grackle_service(client,
303
 
            {'baz': [
304
 
                {'message_id': 'foo', 'headers': {'From': 'me'}}
305
 
            ]}):
 
342
        client = GrackleClient('localhost', 8441)
 
343
        archive = {
 
344
            'baz': [make_message('foo', headers={'From': 'me'})]}
 
345
        with ForkedFakeService.from_client(client, archive):
306
346
            response = client.get_messages('baz', headers=[
307
347
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
308
348
        first_message = response['messages'][0]
310
350
        self.assertEqual({}, first_message['headers'])
311
351
 
312
352
    def test_get_messages_headers_include_headers(self):
313
 
        client = GrackleClient('localhost', 8440)
314
 
        with fake_grackle_service(client,
315
 
            {'baz': [
316
 
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
317
 
            ]}):
 
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):
318
358
            response = client.get_messages('baz', headers=[
319
359
                'From', 'To'])
320
360
        first_message = response['messages'][0]
321
361
        self.assertEqual('foo', first_message['message_id'])
322
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')