~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-03-17 20:57:24 UTC
  • Revision ID: curtis.hovey@canonical.com-20120317205724-2tqfvj1jqtn9fr8t
Converted gracle from a request handler to a wsgi app.
Keep Arrons brilliant ForkedFakeService.

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