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