~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-10 13:46:48 UTC
  • Revision ID: aaron@canonical.com-20120110134648-q991pmjj9jc05mr2
Actual fake service working.

Show diffs side-by-side

added added

removed removed

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