~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Aaron Bentley
  • Date: 2012-01-13 15:14:10 UTC
  • Revision ID: aaron@canonical.com-20120113151410-pexfwaq390oirhr2
Extract GrackleStore.

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
8
import simplejson
10
9
from StringIO import StringIO
11
 
import sys
12
10
from unittest import TestCase
13
11
from urlparse import urlparse
14
12
from urlparse import parse_qs
37
35
 
38
36
 
39
37
class GrackleStore:
40
 
    """A memory-backed message store."""
41
38
 
42
39
    def __init__(self, messages):
43
 
        """Constructor."""
44
40
        self.messages = messages
45
41
 
46
42
    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
43
        query = parse_qs(query_string)
54
44
        parameters = simplejson.loads(query['parameters'][0])
55
45
        order = parameters.get('order')
56
46
        messages = self.messages[archive_id]
57
 
        if order is not None:
 
47
        if order is not None :
58
48
            if order not in SUPPORTED_ORDERS:
59
49
                raise UnsupportedOrder
60
50
            elif order.startswith('thread_'):
70
60
                    messages.extend(thread)
71
61
            else:
72
62
                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
 
63
        messages = [m for m in messages
 
64
                    if 'message_ids' not in parameters or
 
65
                    m['message_id'] in parameters['message_ids']]
93
66
        limit = parameters.get('limit', 100)
94
67
        memo = parameters.get('memo')
95
68
        message_id_indices = dict(
108
81
        else:
109
82
            next_memo = None
110
83
        messages = messages[start:end]
111
 
 
 
84
        new_messages = []
 
85
        for message in messages:
 
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)
112
96
        response = {
113
 
            'messages': messages,
 
97
            'messages': new_messages,
114
98
            'next_memo': next_memo,
115
99
            'previous_memo': previous_memo
116
100
            }
117
101
        return response
118
102
 
119
103
 
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
 
        """
 
104
 
 
105
class ForkedFake:
 
106
 
 
107
    def __init__(self, port, messages=None):
132
108
        self.pid = None
133
109
        self.port = port
134
 
        if messages is None:
135
 
            self.messages = {}
136
 
        else:
137
 
            self.messages = messages
 
110
        self.messages = messages
138
111
        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
112
 
152
113
    def is_ready(self):
153
 
        """Tell the parent process that the server is ready for writes."""
154
114
        os.write(self.write_end, 'asdf')
155
115
 
156
116
    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
117
        pid = os.fork()
162
118
        if pid == 0:
163
119
            self.start_server()
166
122
        return
167
123
 
168
124
    def start_server(self):
169
 
        """Start the HTTP server."""
170
125
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
171
126
        service.store = GrackleStore(self.messages)
172
127
        for archive_id, messages in service.store.messages.iteritems():
173
128
            for message in messages:
174
129
                message.setdefault('headers', {})
175
130
        self.is_ready()
176
 
        if self.write_logs:
177
 
            logging.basicConfig(
178
 
                stream=sys.stderr, level=logging.INFO)
179
131
        service.serve_forever()
180
132
 
181
133
    def __exit__(self, exc_type, exc_val, traceback):
188
140
 
189
141
 
190
142
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
143
 
198
144
    def do_POST(self):
199
 
        """Create a message on POST."""
200
145
        message = self.rfile.read(int(self.headers['content-length']))
201
146
        if message == 'This is a message':
202
147
            self.send_response(httplib.CREATED)
206
151
            self.send_error(httplib.BAD_REQUEST)
207
152
 
208
153
    def do_GET(self):
209
 
        """Retrieve a list of messages on GET."""
210
154
        scheme, netloc, path, params, query_string, fragments = (
211
155
            urlparse(self.path))
212
156
        parts = path.split('/')
222
166
                self.wfile.write('Unsupported order')
223
167
                return
224
168
 
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)
 
169
 
 
170
def fake_grackle_service(client, messages=None):
 
171
    if messages is None:
 
172
        messages = {}
 
173
    return ForkedFake(client.port, messages)
230
174
 
231
175
 
232
176
class TestPutMessage(TestCase):
233
177
 
234
178
    def test_put_message(self):
235
179
        client = GrackleClient('localhost', 8436)
236
 
        with ForkedFakeService.from_client(client):
 
180
        with fake_grackle_service(client):
237
181
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
238
182
            with ExpectedException(Exception, 'wtf'):
239
183
                client.put_message('arch1', 'asdf',
247
191
 
248
192
    def assertMessageIDs(self, ids, messages):
249
193
        self.assertIDOrder(
250
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
194
            sorted(ids), sorted(messages, key=lambda m:m['message_id']))
251
195
 
252
196
    def test_get_messages(self):
253
197
        client = GrackleClient('localhost', 8435)
254
 
        with ForkedFakeService.from_client(client,
 
198
        with fake_grackle_service(client,
255
199
            {'baz':
256
200
            [{'message_id': 'foo'},
257
201
             {'message_id': 'bar'}]}):
263
207
 
264
208
    def test_get_messages_by_id(self):
265
209
        client = GrackleClient('localhost', 8437)
266
 
        with ForkedFakeService.from_client(client,
 
210
        with fake_grackle_service(client,
267
211
            {'baz':
268
212
            [{'message_id': 'foo'},
269
213
             {'message_id': 'bar'}]}):
273
217
 
274
218
    def test_get_messages_batching(self):
275
219
        client = GrackleClient('localhost', 8438)
276
 
        with ForkedFakeService.from_client(client,
 
220
        with fake_grackle_service(client,
277
221
            {'baz':
278
222
            [{'message_id': 'foo'},
279
223
             {'message_id': 'bar'}]}):
288
232
 
289
233
    def get_messages_member_order_test(self, key):
290
234
        client = GrackleClient('localhost', 8439)
291
 
        with ForkedFakeService.from_client(client,
 
235
        with fake_grackle_service(client,
292
236
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
293
237
                 {'message_id': 'bar', key: '2011-03-24'}]}):
294
238
            response = client.get_messages('baz')
307
251
 
308
252
    def test_get_messages_thread_subject_order(self):
309
253
        client = GrackleClient('localhost', 8439)
310
 
        with ForkedFakeService.from_client(client, {'baz': [
 
254
        with fake_grackle_service(client, {'baz': [
311
255
            {'message_id': 'bar', 'subject': 'y'},
312
256
            {'message_id': 'qux', 'subject': 'z'},
313
257
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
321
265
 
322
266
    def test_get_messages_thread_oldest_order(self):
323
267
        client = GrackleClient('localhost', 8439)
324
 
        with ForkedFakeService.from_client(client, {'baz': [
 
268
        with fake_grackle_service(client, {'baz': [
325
269
            {'message_id': 'bar', 'date': 'x'},
326
270
            {'message_id': 'qux', 'date': 'z'},
327
271
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
335
279
 
336
280
    def test_get_messages_thread_newest_order(self):
337
281
        client = GrackleClient('localhost', 8439)
338
 
        with ForkedFakeService.from_client(client, {'baz': [
 
282
        with fake_grackle_service(client, {'baz': [
339
283
            {'message_id': 'bar', 'date': 'x'},
340
284
            {'message_id': 'qux', 'date': 'w'},
341
285
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
350
294
 
351
295
    def test_get_messages_unsupported_order(self):
352
296
        client = GrackleClient('localhost', 8439)
353
 
        with ForkedFakeService.from_client(client,
 
297
        with fake_grackle_service(client,
354
298
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
355
299
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
356
 
            with ExpectedException(UnsupportedOrder, ''):
 
300
            with ExpectedException(UnsupportedOrder):
357
301
                client.get_messages('baz', order='nonsense')
358
302
 
359
303
    def test_get_messages_headers_no_headers(self):
360
304
        client = GrackleClient('localhost', 8440)
361
 
        with ForkedFakeService.from_client(client,
 
305
        with fake_grackle_service(client,
362
306
            {'baz': [
363
307
                {'message_id': 'foo'}
364
308
            ]}):
369
313
        self.assertEqual({}, first_message['headers'])
370
314
 
371
315
    def test_get_messages_headers_exclude_headers(self):
372
 
        client = GrackleClient('localhost', 8441)
373
 
        with ForkedFakeService.from_client(client,
 
316
        client = GrackleClient('localhost', 8440)
 
317
        with fake_grackle_service(client,
374
318
            {'baz': [
375
319
                {'message_id': 'foo', 'headers': {'From': 'me'}}
376
320
            ]}):
381
325
        self.assertEqual({}, first_message['headers'])
382
326
 
383
327
    def test_get_messages_headers_include_headers(self):
384
 
        client = GrackleClient('localhost', 8442)
385
 
        with ForkedFakeService.from_client(client,
 
328
        client = GrackleClient('localhost', 8440)
 
329
        with fake_grackle_service(client,
386
330
            {'baz': [
387
331
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
388
332
            ]}):
393
337
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
394
338
 
395
339
    def test_get_messages_max_body_length(self):
396
 
        client = GrackleClient('localhost', 8443)
397
 
        with ForkedFakeService.from_client(client,
 
340
        client = GrackleClient('localhost', 8440)
 
341
        with fake_grackle_service(client,
398
342
            {'baz': [
399
343
                {'message_id': 'foo', 'body': u'abcdefghi'}
400
344
            ]}):
402
346
        first_message = response['messages'][0]
403
347
        self.assertEqual('abc', first_message['body'])
404
348
 
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'])