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