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