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