~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-11 08:06:03 UTC
  • Revision ID: aaron@canonical.com-20120111080603-fxgo006hthzc89kq
check is PHONY

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
    BaseHTTPRequestHandler,
4
4
    )
5
5
import httplib
6
 
import logging
7
6
import os
8
7
from signal import SIGKILL
9
 
import simplejson
10
8
from StringIO import StringIO
11
 
import sys
12
9
from unittest import TestCase
13
 
from urlparse import urlparse
14
 
from urlparse import parse_qs
15
10
 
16
11
from testtools import ExpectedException
17
12
 
18
13
from grackle.client import (
19
14
    GrackleClient,
20
 
    UnsupportedDisplayType,
21
 
    UnsupportedOrder,
22
15
    )
23
16
 
24
17
 
25
 
def threaded_messages(messages):
26
 
    threads = {}
27
 
    count = 0
28
 
    pending = []
29
 
    for message in messages:
30
 
        if message.get('in_reply_to') is None:
31
 
            threads[message['message_id']] = [message]
32
 
            count += 1
33
 
        else:
34
 
            pending.append(message)
35
 
    for message in pending:
36
 
        threads[message['in_reply_to']].append(message)
37
 
    return threads.values()
38
 
 
39
 
 
40
 
class GrackleStore:
41
 
    """A memory-backed message store."""
42
 
 
43
 
    def __init__(self, messages):
44
 
        """Constructor."""
45
 
        self.messages = messages
46
 
 
47
 
    def get_messages(self, archive_id, query_string):
48
 
        """Return matching messages.
49
 
 
50
 
        :param archive_id: The archive to retrieve from.
51
 
        :param query_string: Contains 'parameters', which is a JSON-format
52
 
            string describing parameters.
53
 
        """
54
 
        query = parse_qs(query_string)
55
 
        parameters = simplejson.loads(query['parameters'][0])
56
 
        order = parameters.get('order')
57
 
        messages = self.messages[archive_id]
58
 
        if order is not None:
59
 
            if order not in SUPPORTED_ORDERS:
60
 
                raise UnsupportedOrder
61
 
            elif order.startswith('thread_'):
62
 
                threaded = threaded_messages(messages)
63
 
                messages = []
64
 
                if order == 'thread_subject':
65
 
                    threaded.sort(key=lambda t: t[0]['subject'])
66
 
                if order == 'thread_oldest':
67
 
                    threaded.sort(key=lambda t: min(m['date'] for m in t))
68
 
                if order == 'thread_newest':
69
 
                    threaded.sort(key=lambda t: max(m['date'] for m in t))
70
 
                for thread in threaded:
71
 
                    messages.extend(thread)
72
 
            else:
73
 
                messages.sort(key=lambda m: m[order])
74
 
        display_type = parameters.get('display_type', 'all')
75
 
        if display_type not in SUPPORTED_DISPLAY_TYPES:
76
 
            raise UnsupportedDisplayType
77
 
        new_messages = []
78
 
        for message in messages:
79
 
            if (not parameters['include_hidden']
80
 
                and message.get('hidden', False)):
81
 
                continue
82
 
 
83
 
            if ('message_ids' in parameters
84
 
                and message['message_id'] not in parameters['message_ids']):
85
 
                continue
86
 
            message = dict(message)
87
 
            if 'headers' in parameters:
88
 
                headers = dict(
89
 
                    (k, v) for k, v in message['headers'].iteritems()
90
 
                    if k in parameters['headers'])
91
 
                message['headers'] = headers
92
 
            max_body = parameters.get('max_body_length')
93
 
            if max_body is not None:
94
 
                message['body'] = message['body'][:max_body]
95
 
            new_messages.append(message)
96
 
        messages = new_messages
97
 
        limit = parameters.get('limit', 100)
98
 
        memo = parameters.get('memo')
99
 
        message_id_indices = dict(
100
 
            (m['message_id'], idx) for idx, m in enumerate(messages))
101
 
        if memo is None:
102
 
            start = 0
103
 
        else:
104
 
            start = message_id_indices[memo.encode('rot13')]
105
 
        if start > 0:
106
 
            previous_memo = messages[start - 1]['message_id'].encode('rot13')
107
 
        else:
108
 
            previous_memo = None
109
 
        end = min(start + limit, len(messages))
110
 
        if end < len(messages):
111
 
            next_memo = messages[end]['message_id'].encode('rot13')
112
 
        else:
113
 
            next_memo = None
114
 
        messages = messages[start:end]
115
 
 
116
 
        response = {
117
 
            'messages': messages,
118
 
            'next_memo': next_memo,
119
 
            'previous_memo': previous_memo
120
 
            }
121
 
        return response
122
 
 
123
 
 
124
 
class ForkedFakeService:
125
 
    """A Grackle service fake, as a ContextManager."""
126
 
 
127
 
    def __init__(self, port, messages=None, write_logs=False):
128
 
        """Constructor.
129
 
 
130
 
        :param port: The tcp port to use.
131
 
        :param messages: A dict of lists of dicts representing messages.  The
132
 
            outer dict represents the archive, the list represents the list of
133
 
            messages for that archive.
134
 
        :param write_logs: If true, log messages will be written to stdout.
135
 
        """
 
18
class Forked:
 
19
 
 
20
    def __init__(self, func_or_method, *args):
 
21
        self.func_or_method = func_or_method
136
22
        self.pid = None
137
 
        self.port = port
138
 
        if messages is None:
139
 
            self.messages = {}
140
 
        else:
141
 
            self.messages = messages
142
 
        self.read_end, self.write_end = os.pipe()
143
 
        self.write_logs = write_logs
144
 
 
145
 
    @staticmethod
146
 
    def from_client(client, messages=None):
147
 
        """Instantiate a ForkedFakeService from the client.
148
 
 
149
 
        :param port: The client to provide service for.
150
 
        :param messages: A dict of lists of dicts representing messages.  The
151
 
            outer dict represents the archive, the list represents the list of
152
 
            messages for that archive.
153
 
        """
154
 
        return ForkedFakeService(client.port, messages)
155
 
 
156
 
    def is_ready(self):
157
 
        """Tell the parent process that the server is ready for writes."""
158
 
        os.write(self.write_end, 'asdf')
 
23
        self.args = args
159
24
 
160
25
    def __enter__(self):
161
 
        """Run the service.
162
 
 
163
 
        Fork and start a server in the child.  Return when the server is ready
164
 
        for use."""
165
26
        pid = os.fork()
166
 
        if pid == 0:
167
 
            self.start_server()
168
 
        self.pid = pid
169
 
        os.read(self.read_end, 1)
170
 
        return
 
27
        if pid != 0:
 
28
            self.pid = pid
 
29
            return
 
30
        self.func_or_method(*self.args)
171
31
 
172
 
    def start_server(self):
173
 
        """Start the HTTP server."""
174
 
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
175
 
        service.store = GrackleStore(self.messages)
176
 
        for archive_id, messages in service.store.messages.iteritems():
177
 
            for message in messages:
178
 
                message.setdefault('headers', {})
179
 
        self.is_ready()
180
 
        if self.write_logs:
181
 
            logging.basicConfig(
182
 
                stream=sys.stderr, level=logging.INFO)
183
 
        service.serve_forever()
184
32
 
185
33
    def __exit__(self, exc_type, exc_val, traceback):
186
34
        os.kill(self.pid, SIGKILL)
187
35
 
188
36
 
189
 
SUPPORTED_DISPLAY_TYPES = set(['all', 'text-only', 'headers-only'])
190
 
 
191
 
 
192
 
SUPPORTED_ORDERS = set(
193
 
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
194
 
     'thread_subject'])
195
 
 
196
 
 
197
37
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
198
 
    """A request handler that forwards to server.store."""
199
 
 
200
 
    def __init__(self, *args, **kwargs):
201
 
        """Constructor.  Sets up logging."""
202
 
        self.logger = logging.getLogger('http')
203
 
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
204
 
 
205
 
    def do_POST(self):
206
 
        """Create a message on POST."""
 
38
 
 
39
    def do_PUT(self):
207
40
        message = self.rfile.read(int(self.headers['content-length']))
208
41
        if message == 'This is a message':
209
42
            self.send_response(httplib.CREATED)
212
45
        else:
213
46
            self.send_error(httplib.BAD_REQUEST)
214
47
 
215
 
    def do_GET(self):
216
 
        """Retrieve a list of messages on GET."""
217
 
        scheme, netloc, path, params, query_string, fragments = (
218
 
            urlparse(self.path))
219
 
        parts = path.split('/')
220
 
        if parts[1] == 'archive':
221
 
            try:
222
 
                response = self.server.store.get_messages(
223
 
                    parts[2], query_string)
224
 
                self.send_response(httplib.OK)
225
 
                self.end_headers()
226
 
                self.wfile.write(simplejson.dumps(response))
227
 
            except UnsupportedOrder:
228
 
                self.send_response(httplib.BAD_REQUEST)
229
 
                self.wfile.write('Unsupported order')
230
 
                return
231
 
 
232
 
    def log_message(self, format, *args):
233
 
        """Override log_message to use standard Python logging."""
234
 
        message = "%s - - [%s] %s\n" % (
235
 
            self.address_string(), self.log_date_time_string(), format % args)
236
 
        self.logger.info(message)
 
48
 
 
49
def run_service(port):
 
50
    service = HTTPServer(('', port), FakeGrackleRequestHandler)
 
51
    service.serve_forever()
 
52
 
237
53
 
238
54
 
239
55
class TestPutMessage(TestCase):
240
56
 
241
57
    def test_put_message(self):
242
 
        client = GrackleClient('localhost', 8436)
243
 
        with ForkedFakeService.from_client(client):
 
58
        client = GrackleClient('localhost', 8435)
 
59
        with Forked(run_service, client.port):
244
60
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
245
61
            with ExpectedException(Exception, 'wtf'):
246
62
                client.put_message('arch1', 'asdf',
247
63
                    StringIO('This is not a message'))
248
 
 
249
 
 
250
 
class TestGetMessages(TestCase):
251
 
 
252
 
    def assertIDOrder(self, ids, messages):
253
 
        self.assertEqual(ids, [m['message_id'] for m in messages])
254
 
 
255
 
    def assertMessageIDs(self, ids, messages):
256
 
        self.assertIDOrder(
257
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
258
 
 
259
 
    def test_get_messages(self):
260
 
        client = GrackleClient('localhost', 8435)
261
 
        with ForkedFakeService.from_client(client,
262
 
            {'baz':
263
 
            [{'message_id': 'foo'},
264
 
             {'message_id': 'bar'}]}):
265
 
            response = client.get_messages('baz')
266
 
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
267
 
            response['messages']))
268
 
        self.assertIs(None, response['next_memo'])
269
 
        self.assertIs(None, response['previous_memo'])
270
 
 
271
 
    def test_get_messages_by_id(self):
272
 
        client = GrackleClient('localhost', 8437)
273
 
        with ForkedFakeService.from_client(client,
274
 
            {'baz':
275
 
            [{'message_id': 'foo'},
276
 
             {'message_id': 'bar'}]}):
277
 
            response = client.get_messages('baz', message_ids=['foo'])
278
 
        message, = response['messages']
279
 
        self.assertEqual('foo', message['message_id'])
280
 
 
281
 
    def test_get_messages_batching(self):
282
 
        client = GrackleClient('localhost', 8438)
283
 
        with ForkedFakeService.from_client(client,
284
 
            {'baz':
285
 
            [{'message_id': 'foo'},
286
 
             {'message_id': 'bar'}]}):
287
 
            response = client.get_messages('baz', limit=1)
288
 
            self.assertEqual(1, len(response['messages']))
289
 
            messages = response['messages']
290
 
            response = client.get_messages(
291
 
                'baz', limit=1, memo=response['next_memo'])
292
 
            self.assertEqual(1, len(response['messages']))
293
 
            messages.extend(response['messages'])
294
 
            self.assertMessageIDs(['foo', 'bar'], messages)
295
 
 
296
 
    def get_messages_member_order_test(self, key):
297
 
        client = GrackleClient('localhost', 8439)
298
 
        with ForkedFakeService.from_client(client,
299
 
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
300
 
                 {'message_id': 'bar', key: '2011-03-24'}]}):
301
 
            response = client.get_messages('baz')
302
 
            self.assertIDOrder(['foo', 'bar'], response['messages'])
303
 
            response = client.get_messages('baz', order=key)
304
 
            self.assertIDOrder(['bar', 'foo'], response['messages'])
305
 
 
306
 
    def test_get_messages_date_order(self):
307
 
        self.get_messages_member_order_test('date')
308
 
 
309
 
    def test_get_messages_author_order(self):
310
 
        self.get_messages_member_order_test('author')
311
 
 
312
 
    def test_get_messages_subject_order(self):
313
 
        self.get_messages_member_order_test('subject')
314
 
 
315
 
    def test_get_messages_thread_subject_order(self):
316
 
        client = GrackleClient('localhost', 8439)
317
 
        with ForkedFakeService.from_client(client, {'baz': [
318
 
            {'message_id': 'bar', 'subject': 'y'},
319
 
            {'message_id': 'qux', 'subject': 'z'},
320
 
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
321
 
            ]}):
322
 
            response = client.get_messages('baz')
323
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
324
 
            response = client.get_messages('baz', order='subject')
325
 
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
326
 
            response = client.get_messages('baz', order='thread_subject')
327
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
328
 
 
329
 
    def test_get_messages_thread_oldest_order(self):
330
 
        client = GrackleClient('localhost', 8439)
331
 
        with ForkedFakeService.from_client(client, {'baz': [
332
 
            {'message_id': 'bar', 'date': 'x'},
333
 
            {'message_id': 'qux', 'date': 'z'},
334
 
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
335
 
            ]}):
336
 
            response = client.get_messages('baz')
337
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
338
 
            response = client.get_messages('baz', order='date')
339
 
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
340
 
            response = client.get_messages('baz', order='thread_oldest')
341
 
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
342
 
 
343
 
    def test_get_messages_thread_newest_order(self):
344
 
        client = GrackleClient('localhost', 8439)
345
 
        with ForkedFakeService.from_client(client, {'baz': [
346
 
            {'message_id': 'bar', 'date': 'x'},
347
 
            {'message_id': 'qux', 'date': 'w'},
348
 
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
349
 
            {'message_id': 'baz', 'date': 'z', 'in_reply_to': 'qux'},
350
 
            ]}):
351
 
            response = client.get_messages('baz', order='date')
352
 
            self.assertIDOrder(
353
 
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
354
 
            response = client.get_messages('baz', order='thread_newest')
355
 
            self.assertIDOrder(
356
 
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
357
 
 
358
 
    def test_get_messages_unsupported_order(self):
359
 
        client = GrackleClient('localhost', 8439)
360
 
        with ForkedFakeService.from_client(client,
361
 
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
362
 
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
363
 
            with ExpectedException(UnsupportedOrder, ''):
364
 
                client.get_messages('baz', order='nonsense')
365
 
 
366
 
    def test_get_messages_headers_no_headers(self):
367
 
        client = GrackleClient('localhost', 8440)
368
 
        with ForkedFakeService.from_client(client,
369
 
            {'baz': [
370
 
                {'message_id': 'foo'}
371
 
            ]}):
372
 
            response = client.get_messages('baz', headers=[
373
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
374
 
        first_message = response['messages'][0]
375
 
        self.assertEqual('foo', first_message['message_id'])
376
 
        self.assertEqual({}, first_message['headers'])
377
 
 
378
 
    def test_get_messages_headers_exclude_headers(self):
379
 
        client = GrackleClient('localhost', 8441)
380
 
        with ForkedFakeService.from_client(client,
381
 
            {'baz': [
382
 
                {'message_id': 'foo', 'headers': {'From': 'me'}}
383
 
            ]}):
384
 
            response = client.get_messages('baz', headers=[
385
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
386
 
        first_message = response['messages'][0]
387
 
        self.assertEqual('foo', first_message['message_id'])
388
 
        self.assertEqual({}, first_message['headers'])
389
 
 
390
 
    def test_get_messages_headers_include_headers(self):
391
 
        client = GrackleClient('localhost', 8442)
392
 
        with ForkedFakeService.from_client(client,
393
 
            {'baz': [
394
 
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
395
 
            ]}):
396
 
            response = client.get_messages('baz', headers=[
397
 
                'From', 'To'])
398
 
        first_message = response['messages'][0]
399
 
        self.assertEqual('foo', first_message['message_id'])
400
 
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
401
 
 
402
 
    def test_get_messages_max_body_length(self):
403
 
        client = GrackleClient('localhost', 8443)
404
 
        with ForkedFakeService.from_client(client,
405
 
            {'baz': [
406
 
                {'message_id': 'foo', 'body': u'abcdefghi'}
407
 
            ]}):
408
 
            response = client.get_messages('baz', max_body_length=3)
409
 
        first_message = response['messages'][0]
410
 
        self.assertEqual('abc', first_message['body'])
411
 
 
412
 
    def test_include_hidden(self):
413
 
        client = GrackleClient('localhost', 8444)
414
 
        with ForkedFakeService.from_client(client,
415
 
            {'baz': [
416
 
                {'message_id': 'foo', 'hidden': True},
417
 
                {'message_id': 'bar', 'hidden': False}
418
 
            ]}):
419
 
            response = client.get_messages('baz', include_hidden=True)
420
 
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
421
 
            response = client.get_messages('baz', include_hidden=False)
422
 
            self.assertMessageIDs(['bar'], response['messages'])
423
 
 
424
 
    def test_display_type_unknown_value(self):
425
 
        client = GrackleClient('localhost', 8445)
426
 
        with ForkedFakeService.from_client(client,
427
 
            {'baz': [
428
 
                {'message_id': 'foo', 'body': u'abcdefghi'}
429
 
            ]}):
430
 
            with ExpectedException(UnsupportedDisplayType, ''):
431
 
                client.get_messages('baz', display_type='unknown')