~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('..')
120
            except:
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))
35.1.8 by Curtis Hovey
Use the exception __doc__ to ensure client and server can match exceptions.
284
            except UnsupportedOrder:
285
                self.send_response(
286
                    httplib.BAD_REQUEST, UnsupportedOrder.__doc__)
287
                return
35.1.7 by Curtis Hovey
Moved the handling of unsupported display_type to server.
288
            except UnsupportedDisplayType:
289
                self.send_response(
35.1.8 by Curtis Hovey
Use the exception __doc__ to ensure client and server can match exceptions.
290
                    httplib.BAD_REQUEST, UnsupportedDisplayType.__doc__)
28 by Aaron Bentley
Extract GrackleStore.
291
                return
13 by Aaron Bentley
Retrieve messages.
292
32 by Aaron Bentley
Switch test HTTP server to standard Python logging.
293
    def log_message(self, format, *args):
34 by Aaron Bentley
Cleanup
294
        """Override log_message to use standard Python logging."""
32 by Aaron Bentley
Switch test HTTP server to standard Python logging.
295
        message = "%s - - [%s] %s\n" % (
35.1.1 by Curtis Hovey
Hush lint.
296
            self.address_string(), self.log_date_time_string(), format % args)
32 by Aaron Bentley
Switch test HTTP server to standard Python logging.
297
        self.logger.info(message)
298
5 by Aaron Bentley
Actual fake service working.
299
3 by Aaron Bentley
Add test framework.
300
class TestPutMessage(TestCase):
301
302
    def test_put_message(self):
12 by Aaron Bentley
Use pipe to ensure we only use HTTP once it's running.
303
        client = GrackleClient('localhost', 8436)
35.1.3 by Curtis Hovey
Renamed ForkedFake => ForkedFakeService.
304
        with ForkedFakeService.from_client(client):
7 by Aaron Bentley
Fix URLs etc.
305
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
5 by Aaron Bentley
Actual fake service working.
306
            with ExpectedException(Exception, 'wtf'):
7 by Aaron Bentley
Fix URLs etc.
307
                client.put_message('arch1', 'asdf',
308
                    StringIO('This is not a message'))
11 by Aaron Bentley
Start working on GET.
309
310
311
class TestGetMessages(TestCase):
312
20 by Aaron Bentley
Support order by date
313
    def assertIDOrder(self, ids, messages):
314
        self.assertEqual(ids, [m['message_id'] for m in messages])
315
19 by Aaron Bentley
Implement memo/limit support.
316
    def assertMessageIDs(self, ids, messages):
20 by Aaron Bentley
Support order by date
317
        self.assertIDOrder(
35.1.1 by Curtis Hovey
Hush lint.
318
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
19 by Aaron Bentley
Implement memo/limit support.
319
11 by Aaron Bentley
Start working on GET.
320
    def test_get_messages(self):
321
        client = GrackleClient('localhost', 8435)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
322
        archive = {
323
            'baz': [make_message('foo'), make_message('bar')]}
324
        with ForkedFakeService.from_client(client, archive):
15 by Aaron Bentley
Test filtering by message-id.
325
            response = client.get_messages('baz')
17 by Aaron Bentley
Switch hyphens to underscores.
326
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
16 by Aaron Bentley
Include next_memo, previous_memo in get_messages response.
327
            response['messages']))
328
        self.assertIs(None, response['next_memo'])
329
        self.assertIs(None, response['previous_memo'])
15 by Aaron Bentley
Test filtering by message-id.
330
331
    def test_get_messages_by_id(self):
332
        client = GrackleClient('localhost', 8437)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
333
        archive = {
334
            'baz': [make_message('foo'), make_message('bar')]}
335
        with ForkedFakeService.from_client(client, archive):
16 by Aaron Bentley
Include next_memo, previous_memo in get_messages response.
336
            response = client.get_messages('baz', message_ids=['foo'])
337
        message, = response['messages']
17 by Aaron Bentley
Switch hyphens to underscores.
338
        self.assertEqual('foo', message['message_id'])
19 by Aaron Bentley
Implement memo/limit support.
339
340
    def test_get_messages_batching(self):
20 by Aaron Bentley
Support order by date
341
        client = GrackleClient('localhost', 8438)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
342
        archive = {'baz': [make_message('foo'), make_message('bar')]}
343
        with ForkedFakeService.from_client(client, archive):
19 by Aaron Bentley
Implement memo/limit support.
344
            response = client.get_messages('baz', limit=1)
345
            self.assertEqual(1, len(response['messages']))
346
            messages = response['messages']
347
            response = client.get_messages(
348
                'baz', limit=1, memo=response['next_memo'])
349
            self.assertEqual(1, len(response['messages']))
350
            messages.extend(response['messages'])
351
            self.assertMessageIDs(['foo', 'bar'], messages)
20 by Aaron Bentley
Support order by date
352
22 by Aaron Bentley
Order by thread subject.
353
    def get_messages_member_order_test(self, key):
20 by Aaron Bentley
Support order by date
354
        client = GrackleClient('localhost', 8439)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
355
        archive = {
356
            'baz': [
357
                make_message('foo', headers={key: '2011-03-25'}),
358
                make_message('bar', headers={key: '2011-03-24'}),
359
             ]}
360
        with ForkedFakeService.from_client(client, archive):
20 by Aaron Bentley
Support order by date
361
            response = client.get_messages('baz')
362
            self.assertIDOrder(['foo', 'bar'], response['messages'])
22 by Aaron Bentley
Order by thread subject.
363
            response = client.get_messages('baz', order=key)
20 by Aaron Bentley
Support order by date
364
            self.assertIDOrder(['bar', 'foo'], response['messages'])
21 by Aaron Bentley
Test unsupported orders.
365
22 by Aaron Bentley
Order by thread subject.
366
    def test_get_messages_date_order(self):
367
        self.get_messages_member_order_test('date')
368
369
    def test_get_messages_author_order(self):
370
        self.get_messages_member_order_test('author')
371
372
    def test_get_messages_subject_order(self):
373
        self.get_messages_member_order_test('subject')
374
375
    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.
376
        archive = {
377
            'baz': [
378
                make_message('bar', headers={'subject': 'y'}),
379
                make_message('qux', headers={'subject': 'z'}),
380
                make_message('foo', headers={'subject': 'x',
381
                                             'in-reply-to': 'qux'}),
382
             ]}
22 by Aaron Bentley
Order by thread subject.
383
        client = GrackleClient('localhost', 8439)
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
384
        with ForkedFakeService.from_client(client, archive):
22 by Aaron Bentley
Order by thread subject.
385
            response = client.get_messages('baz')
386
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
387
            response = client.get_messages('baz', order='subject')
388
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
389
            response = client.get_messages('baz', order='thread_subject')
390
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
391
23 by Aaron Bentley
Support thread_oldest order.
392
    def test_get_messages_thread_oldest_order(self):
393
        client = GrackleClient('localhost', 8439)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
394
        archive = {
395
            'baz': [
396
                make_message('bar', headers={'date': 'x'}),
397
                make_message('qux', headers={'date': 'z'}),
398
                make_message('foo', headers={'date': 'y',
399
                                             'in-reply-to': 'qux'}),
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
400
            ]}
401
        with ForkedFakeService.from_client(client, archive):
23 by Aaron Bentley
Support thread_oldest order.
402
            response = client.get_messages('baz')
403
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
404
            response = client.get_messages('baz', order='date')
405
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
406
            response = client.get_messages('baz', order='thread_oldest')
407
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
408
24 by Aaron Bentley
Support thread_newest threading.
409
    def test_get_messages_thread_newest_order(self):
410
        client = GrackleClient('localhost', 8439)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
411
        archive = {
412
            'baz': [
413
                make_message('bar', headers={'date': 'x'}),
414
                make_message('qux', headers={'date': 'w'}),
415
                make_message('foo', headers={'date': 'y',
416
                                             'in-reply-to': 'bar'}),
417
                make_message('baz', headers={'date': 'z',
418
                                             'in-reply-to': 'qux'}),
419
            ]}
420
        with ForkedFakeService.from_client(client, archive):
24 by Aaron Bentley
Support thread_newest threading.
421
            response = client.get_messages('baz', order='date')
422
            self.assertIDOrder(
423
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
424
            response = client.get_messages('baz', order='thread_newest')
425
            self.assertIDOrder(
426
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
427
21 by Aaron Bentley
Test unsupported orders.
428
    def test_get_messages_unsupported_order(self):
429
        client = GrackleClient('localhost', 8439)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
430
        archive = {
431
            'baz': [
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
432
                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.
433
                make_message('foo', headers={'date': '2011-03-24'}),
434
            ]}
435
        with ForkedFakeService.from_client(client, archive):
35 by William Grant
Fix test.
436
            with ExpectedException(UnsupportedOrder, ''):
21 by Aaron Bentley
Test unsupported orders.
437
                client.get_messages('baz', order='nonsense')
27 by Aaron Bentley
get_messages supports header parameter.
438
439
    def test_get_messages_headers_no_headers(self):
440
        client = GrackleClient('localhost', 8440)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
441
        archive = {'baz': [make_message('foo')]}
442
        with ForkedFakeService.from_client(client, archive):
27 by Aaron Bentley
get_messages supports header parameter.
443
            response = client.get_messages('baz', headers=[
444
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
445
        first_message = response['messages'][0]
446
        self.assertEqual('foo', first_message['message_id'])
447
        self.assertEqual({}, first_message['headers'])
448
449
    def test_get_messages_headers_exclude_headers(self):
29 by Aaron Bentley
implement include_hidden.
450
        client = GrackleClient('localhost', 8441)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
451
        archive = {
452
            'baz': [make_message('foo', headers={'From': 'me'})]}
453
        with ForkedFakeService.from_client(client, archive):
27 by Aaron Bentley
get_messages supports header parameter.
454
            response = client.get_messages('baz', headers=[
455
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
456
        first_message = response['messages'][0]
457
        self.assertEqual('foo', first_message['message_id'])
458
        self.assertEqual({}, first_message['headers'])
459
460
    def test_get_messages_headers_include_headers(self):
29 by Aaron Bentley
implement include_hidden.
461
        client = GrackleClient('localhost', 8442)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
462
        archive = {
463
            'baz': [
464
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
465
        with ForkedFakeService.from_client(client, archive):
27 by Aaron Bentley
get_messages supports header parameter.
466
            response = client.get_messages('baz', headers=[
467
                'From', 'To'])
468
        first_message = response['messages'][0]
469
        self.assertEqual('foo', first_message['message_id'])
470
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
28 by Aaron Bentley
Extract GrackleStore.
471
472
    def test_get_messages_max_body_length(self):
29 by Aaron Bentley
implement include_hidden.
473
        client = GrackleClient('localhost', 8443)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
474
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
475
        with ForkedFakeService.from_client(client, archive):
28 by Aaron Bentley
Extract GrackleStore.
476
            response = client.get_messages('baz', max_body_length=3)
477
        first_message = response['messages'][0]
478
        self.assertEqual('abc', first_message['body'])
479
29 by Aaron Bentley
implement include_hidden.
480
    def test_include_hidden(self):
481
        client = GrackleClient('localhost', 8444)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
482
        archive = {
483
            'baz': [
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
484
                make_message('foo', hidden=True),
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
485
                make_message('bar', hidden=False),
486
            ]}
487
        with ForkedFakeService.from_client(client, archive):
29 by Aaron Bentley
implement include_hidden.
488
            response = client.get_messages('baz', include_hidden=True)
489
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
490
            response = client.get_messages('baz', include_hidden=False)
491
            self.assertMessageIDs(['bar'], response['messages'])
35.1.4 by Curtis Hovey
Added SUPPORTED_DISPLAY_TYPES.
492
493
    def test_display_type_unknown_value(self):
35.1.5 by Curtis Hovey
Moved the display_type arg.
494
        client = GrackleClient('localhost', 8445)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
495
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
496
        with ForkedFakeService.from_client(client, archive):
35.1.4 by Curtis Hovey
Added SUPPORTED_DISPLAY_TYPES.
497
            with ExpectedException(UnsupportedDisplayType, ''):
498
                client.get_messages('baz', display_type='unknown')
35.1.6 by Curtis Hovey
Added display_type == 'headers-only' support.
499
500
    def test_display_type_headers_only(self):
35.1.9 by Curtis Hovey
Added support for display_type == 'text-only'.
501
        client = GrackleClient('localhost', 8446)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
502
        archive = {
503
            'baz': [
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
504
                make_message('foo', body=u'abcdefghi',
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
505
                             headers={'From': 'me', 'To': 'you'})]}
506
        with ForkedFakeService.from_client(client, archive):
35.1.6 by Curtis Hovey
Added display_type == 'headers-only' support.
507
            response = client.get_messages('baz', display_type='headers-only')
508
        first_message = response['messages'][0]
509
        self.assertEqual('foo', first_message['message_id'])
35.1.13 by Curtis Hovey
Use make_message() to make test data consistent.
510
        self.assertEqual(
511
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
512
            first_message['headers'])
35.1.6 by Curtis Hovey
Added display_type == 'headers-only' support.
513
        self.assertNotIn('body', first_message)
35.1.9 by Curtis Hovey
Added support for display_type == 'text-only'.
514
515
    def test_display_type_text_only(self):
516
        client = GrackleClient('localhost', 8446)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
517
        archive = {
518
            'baz': [
35.1.12 by Curtis Hovey
Renamed make_message => make_mime_message.
519
                make_mime_message(
35.1.11 by Curtis Hovey
Reanme make_json_message => make_message.
520
                    'foo', 'abcdefghi',
35.1.9 by Curtis Hovey
Added support for display_type == 'text-only'.
521
                    headers={'From': 'me', 'To': 'you'},
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
522
                    attachment_type='text/x-diff')]}
523
        with ForkedFakeService.from_client(client, archive):
35.1.9 by Curtis Hovey
Added support for display_type == 'text-only'.
524
            response = client.get_messages('baz', display_type='text-only')
525
        first_message = response['messages'][0]
526
        self.assertEqual('foo', first_message['message_id'])
527
        self.assertEqual('me', first_message['headers']['From'])
528
        self.assertEqual('you', first_message['headers']['To'])
35.1.10 by Curtis Hovey
Added support for display_type == 'all'.
529
        self.assertEqual('abcdefghi', first_message['body'])
530
531
    def test_display_type_all(self):
532
        client = GrackleClient('localhost', 8447)
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
533
        archive = {
534
            'baz': [
35.1.12 by Curtis Hovey
Renamed make_message => make_mime_message.
535
                make_mime_message(
35.1.11 by Curtis Hovey
Reanme make_json_message => make_message.
536
                    'foo', 'abcdefghi',
35.1.10 by Curtis Hovey
Added support for display_type == 'all'.
537
                    headers={'From': 'me', 'To': 'you'},
35.1.14 by Curtis Hovey
Create the archive outside of the call to create the server.
538
                    attachment_type='text/x-diff')]}
539
        with ForkedFakeService.from_client(client, archive):
35.1.10 by Curtis Hovey
Added support for display_type == 'all'.
540
            response = client.get_messages('baz', display_type='all')
541
        first_message = response['messages'][0]
542
        self.assertEqual('foo', first_message['message_id'])
543
        self.assertEqual('me', first_message['headers']['From'])
544
        self.assertEqual('you', first_message['headers']['To'])
545
        self.assertEqual(
546
            'abcdefghi\n\nattactment data.', first_message['body'])
38 by Curtis Hovey
Added basic handling of date_range.
547
548
    def test_date_range(self):
549
        client = GrackleClient('localhost', 8448)
550
        archive = {
551
            'baz': [
552
                make_mime_message(
553
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
554
                make_mime_message(
555
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
556
                make_mime_message(
557
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
558
                make_mime_message(
559
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
560
                make_mime_message(
561
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
562
                    ]}
563
        with ForkedFakeService.from_client(client, archive):
564
            response = client.get_messages(
565
                'baz', date_range='2012-01-01..2012-01-31')
566
        ids = sorted(m['message_id'] for m in response['messages'])
567
        self.assertEqual(['bar', 'naf', 'qux'], ids)