~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: William Grant
  • Date: 2012-01-25 06:19:56 UTC
  • Revision ID: william.grant@canonical.com-20120125061956-4tltjt6a4xf5yufj
Fix test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
    BaseHTTPRequestHandler,
4
4
    )
5
5
import httplib
 
6
import logging
6
7
import os
7
8
from signal import SIGKILL
8
9
import simplejson
9
10
from StringIO import StringIO
 
11
import sys
10
12
from unittest import TestCase
11
13
from urlparse import urlparse
12
14
from urlparse import parse_qs
34
36
    return threads.values()
35
37
 
36
38
 
37
 
class ForkedFake:
 
39
class GrackleStore:
 
40
    """A memory-backed message store."""
38
41
 
39
 
    def __init__(self, port, messages=None):
40
 
        self.pid = None
41
 
        self.port = port
 
42
    def __init__(self, messages):
 
43
        """Constructor."""
42
44
        self.messages = messages
43
 
        self.read_end, self.write_end = os.pipe()
44
 
 
45
 
    def is_ready(self):
46
 
        os.write(self.write_end, 'asdf')
47
 
 
48
 
    def __enter__(self):
49
 
        pid = os.fork()
50
 
        if pid == 0:
51
 
            self.start_server()
52
 
        self.pid = pid
53
 
        os.read(self.read_end, 1)
54
 
        return
55
 
 
56
 
    def start_server(self):
57
 
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
58
 
        service.messages = self.messages
59
 
        self.is_ready()
60
 
        service.serve_forever()
61
 
 
62
 
    def __exit__(self, exc_type, exc_val, traceback):
63
 
        os.kill(self.pid, SIGKILL)
64
 
 
65
 
 
66
 
SUPPORTED_ORDERS = set(
67
 
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
68
 
     'thread_subject'])
69
 
 
70
 
 
71
 
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
72
 
 
73
 
    def do_POST(self):
74
 
        message = self.rfile.read(int(self.headers['content-length']))
75
 
        if message == 'This is a message':
76
 
            self.send_response(httplib.CREATED)
77
 
            self.end_headers()
78
 
            self.wfile.close()
79
 
        else:
80
 
            self.send_error(httplib.BAD_REQUEST)
81
 
 
82
 
    def do_GET(self):
83
 
        scheme, netloc, path, params, query_string, fragments = (
84
 
            urlparse(self.path))
85
 
        archive = os.path.split(path)[1]
 
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
        """
86
53
        query = parse_qs(query_string)
87
54
        parameters = simplejson.loads(query['parameters'][0])
88
55
        order = parameters.get('order')
89
 
        messages = self.server.messages[archive]
 
56
        messages = self.messages[archive_id]
90
57
        if order is not None :
91
58
            if order not in SUPPORTED_ORDERS:
92
 
                self.send_response(httplib.BAD_REQUEST)
93
 
                self.wfile.write('Unsupported order')
94
 
                return
 
59
                raise UnsupportedOrder
95
60
            elif order.startswith('thread_'):
96
61
                threaded = threaded_messages(messages)
97
62
                messages = []
105
70
                    messages.extend(thread)
106
71
            else:
107
72
                messages.sort(key=lambda m: m[order])
108
 
        messages = [m for m in messages
109
 
                    if 'message_ids' not in parameters or
110
 
                    m['message_id'] in parameters['message_ids']]
111
 
        self.send_response(httplib.OK)
112
 
        self.end_headers()
 
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
113
94
        limit = parameters.get('limit', 100)
114
95
        memo = parameters.get('memo')
115
96
        message_id_indices = dict(
128
109
        else:
129
110
            next_memo = None
130
111
        messages = messages[start:end]
 
112
 
131
113
        response = {
132
114
            'messages': messages,
133
115
            'next_memo': next_memo,
134
116
            'previous_memo': previous_memo
135
117
            }
136
 
        self.wfile.write(simplejson.dumps(response))
137
 
 
138
 
 
139
 
def fake_grackle_service(client, messages=None):
140
 
    if messages is None:
141
 
        messages = {}
142
 
    return ForkedFake(client.port, messages)
 
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
        """
 
133
        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
 
 
157
    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
        pid = os.fork()
 
163
        if pid == 0:
 
164
            self.start_server()
 
165
        self.pid = pid
 
166
        os.read(self.read_end, 1)
 
167
        return
 
168
 
 
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
 
 
182
    def __exit__(self, exc_type, exc_val, traceback):
 
183
        os.kill(self.pid, SIGKILL)
 
184
 
 
185
 
 
186
SUPPORTED_ORDERS = set(
 
187
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
 
188
     'thread_subject'])
 
189
 
 
190
 
 
191
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
 
 
199
    def do_POST(self):
 
200
        """Create a message on POST."""
 
201
        message = self.rfile.read(int(self.headers['content-length']))
 
202
        if message == 'This is a message':
 
203
            self.send_response(httplib.CREATED)
 
204
            self.end_headers()
 
205
            self.wfile.close()
 
206
        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)
143
231
 
144
232
 
145
233
class TestPutMessage(TestCase):
146
234
 
147
235
    def test_put_message(self):
148
236
        client = GrackleClient('localhost', 8436)
149
 
        with fake_grackle_service(client):
 
237
        with ForkedFake.from_client(client):
150
238
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
151
239
            with ExpectedException(Exception, 'wtf'):
152
240
                client.put_message('arch1', 'asdf',
164
252
 
165
253
    def test_get_messages(self):
166
254
        client = GrackleClient('localhost', 8435)
167
 
        with fake_grackle_service(client,
 
255
        with ForkedFake.from_client(client,
168
256
            {'baz':
169
257
            [{'message_id': 'foo'},
170
258
             {'message_id': 'bar'}]}):
176
264
 
177
265
    def test_get_messages_by_id(self):
178
266
        client = GrackleClient('localhost', 8437)
179
 
        with fake_grackle_service(client,
 
267
        with ForkedFake.from_client(client,
180
268
            {'baz':
181
269
            [{'message_id': 'foo'},
182
270
             {'message_id': 'bar'}]}):
186
274
 
187
275
    def test_get_messages_batching(self):
188
276
        client = GrackleClient('localhost', 8438)
189
 
        with fake_grackle_service(client,
 
277
        with ForkedFake.from_client(client,
190
278
            {'baz':
191
279
            [{'message_id': 'foo'},
192
280
             {'message_id': 'bar'}]}):
201
289
 
202
290
    def get_messages_member_order_test(self, key):
203
291
        client = GrackleClient('localhost', 8439)
204
 
        with fake_grackle_service(client,
 
292
        with ForkedFake.from_client(client,
205
293
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
206
294
                 {'message_id': 'bar', key: '2011-03-24'}]}):
207
295
            response = client.get_messages('baz')
220
308
 
221
309
    def test_get_messages_thread_subject_order(self):
222
310
        client = GrackleClient('localhost', 8439)
223
 
        with fake_grackle_service(client, {'baz': [
 
311
        with ForkedFake.from_client(client, {'baz': [
224
312
            {'message_id': 'bar', 'subject': 'y'},
225
313
            {'message_id': 'qux', 'subject': 'z'},
226
314
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
234
322
 
235
323
    def test_get_messages_thread_oldest_order(self):
236
324
        client = GrackleClient('localhost', 8439)
237
 
        with fake_grackle_service(client, {'baz': [
 
325
        with ForkedFake.from_client(client, {'baz': [
238
326
            {'message_id': 'bar', 'date': 'x'},
239
327
            {'message_id': 'qux', 'date': 'z'},
240
328
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
248
336
 
249
337
    def test_get_messages_thread_newest_order(self):
250
338
        client = GrackleClient('localhost', 8439)
251
 
        with fake_grackle_service(client, {'baz': [
 
339
        with ForkedFake.from_client(client, {'baz': [
252
340
            {'message_id': 'bar', 'date': 'x'},
253
341
            {'message_id': 'qux', 'date': 'w'},
254
342
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
263
351
 
264
352
    def test_get_messages_unsupported_order(self):
265
353
        client = GrackleClient('localhost', 8439)
266
 
        with fake_grackle_service(client,
 
354
        with ForkedFake.from_client(client,
267
355
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
268
356
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
269
 
            with ExpectedException(UnsupportedOrder):
 
357
            with ExpectedException(UnsupportedOrder, ''):
270
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