~didrocks/unity/altf10

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