~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 13:52:09 UTC
  • Revision ID: aaron@canonical.com-20120113135209-3s1l4hu25wjyq6jj
Fix grackle-put-message.

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
36
34
    return threads.values()
37
35
 
38
36
 
39
 
class GrackleStore:
40
 
    """A memory-backed message store."""
 
37
class ForkedFake:
41
38
 
42
 
    def __init__(self, messages):
43
 
        """Constructor."""
 
39
    def __init__(self, port, messages=None):
 
40
        self.pid = None
 
41
        self.port = port
44
42
        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
 
        """
 
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]
53
86
        query = parse_qs(query_string)
54
87
        parameters = simplejson.loads(query['parameters'][0])
55
88
        order = parameters.get('order')
56
 
        messages = self.messages[archive_id]
57
 
        if order is not None:
 
89
        messages = self.server.messages[archive]
 
90
        if order is not None :
58
91
            if order not in SUPPORTED_ORDERS:
59
 
                raise UnsupportedOrder
 
92
                self.send_response(httplib.BAD_REQUEST)
 
93
                self.wfile.write('Unsupported order')
 
94
                return
60
95
            elif order.startswith('thread_'):
61
96
                threaded = threaded_messages(messages)
62
97
                messages = []
70
105
                    messages.extend(thread)
71
106
            else:
72
107
                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
 
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()
93
113
        limit = parameters.get('limit', 100)
94
114
        memo = parameters.get('memo')
95
115
        message_id_indices = dict(
108
128
        else:
109
129
            next_memo = None
110
130
        messages = messages[start:end]
111
 
 
112
131
        response = {
113
132
            'messages': messages,
114
133
            'next_memo': next_memo,
115
134
            'previous_memo': previous_memo
116
135
            }
117
 
        return response
118
 
 
119
 
 
120
 
class ForkedFake:
121
 
    """A Grackle service fake, as a ContextManager."""
122
 
 
123
 
    def __init__(self, port, messages=None, write_logs=False):
124
 
        """Constructor.
125
 
        :param port: The tcp port to use
126
 
        :param messages: A dict of lists of dicts representing messages.  The
127
 
            outer dict represents the archive, the list represents the list of
128
 
            messages for that archive.
129
 
        :param write_logs: If true, log messages will be written to stdout.
130
 
        """
131
 
        self.pid = None
132
 
        self.port = port
133
 
        if messages is None:
134
 
            self.messages = {}
135
 
        else:
136
 
            self.messages = messages
137
 
        self.read_end, self.write_end = os.pipe()
138
 
        self.write_logs = write_logs
139
 
 
140
 
    @staticmethod
141
 
    def from_client(client, messages=None):
142
 
        """Instantiate a ForkedFake from the client.
143
 
 
144
 
        :param port: The client  to provide service for.
145
 
        :param messages: A dict of lists of dicts representing messages.  The
146
 
            outer dict represents the archive, the list represents the list of
147
 
            messages for that archive.
148
 
        """
149
 
        return ForkedFake(client.port, messages)
150
 
 
151
 
    def is_ready(self):
152
 
        """Tell the parent process that the server is ready for writes."""
153
 
        os.write(self.write_end, 'asdf')
154
 
 
155
 
    def __enter__(self):
156
 
        """Run the service.
157
 
 
158
 
        Fork and start a server in the child.  Return when the server is ready
159
 
        for use."""
160
 
        pid = os.fork()
161
 
        if pid == 0:
162
 
            self.start_server()
163
 
        self.pid = pid
164
 
        os.read(self.read_end, 1)
165
 
        return
166
 
 
167
 
    def start_server(self):
168
 
        """Start the HTTP server."""
169
 
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
170
 
        service.store = GrackleStore(self.messages)
171
 
        for archive_id, messages in service.store.messages.iteritems():
172
 
            for message in messages:
173
 
                message.setdefault('headers', {})
174
 
        self.is_ready()
175
 
        if self.write_logs:
176
 
            logging.basicConfig(
177
 
                stream=sys.stderr, level=logging.INFO)
178
 
        service.serve_forever()
179
 
 
180
 
    def __exit__(self, exc_type, exc_val, traceback):
181
 
        os.kill(self.pid, SIGKILL)
182
 
 
183
 
 
184
 
SUPPORTED_ORDERS = set(
185
 
    ['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
186
 
     'thread_subject'])
187
 
 
188
 
 
189
 
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
190
 
    """A request handler that forwards to server.store."""
191
 
 
192
 
    def __init__(self, *args, **kwargs):
193
 
        """Constructor.  Sets up logging."""
194
 
        self.logger = logging.getLogger('http')
195
 
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
196
 
 
197
 
    def do_POST(self):
198
 
        """Create a message on POST."""
199
 
        message = self.rfile.read(int(self.headers['content-length']))
200
 
        if message == 'This is a message':
201
 
            self.send_response(httplib.CREATED)
202
 
            self.end_headers()
203
 
            self.wfile.close()
204
 
        else:
205
 
            self.send_error(httplib.BAD_REQUEST)
206
 
 
207
 
    def do_GET(self):
208
 
        """Retrieve a list of messages on GET."""
209
 
        scheme, netloc, path, params, query_string, fragments = (
210
 
            urlparse(self.path))
211
 
        parts = path.split('/')
212
 
        if parts[1] == 'archive':
213
 
            try:
214
 
                response = self.server.store.get_messages(
215
 
                    parts[2], query_string)
216
 
                self.send_response(httplib.OK)
217
 
                self.end_headers()
218
 
                self.wfile.write(simplejson.dumps(response))
219
 
            except UnsupportedOrder:
220
 
                self.send_response(httplib.BAD_REQUEST)
221
 
                self.wfile.write('Unsupported order')
222
 
                return
223
 
 
224
 
    def log_message(self, format, *args):
225
 
        """Override log_message to use standard Python logging."""
226
 
        message = "%s - - [%s] %s\n" % (
227
 
            self.address_string(), self.log_date_time_string(), format % args)
228
 
        self.logger.info(message)
 
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)
229
143
 
230
144
 
231
145
class TestPutMessage(TestCase):
232
146
 
233
147
    def test_put_message(self):
234
148
        client = GrackleClient('localhost', 8436)
235
 
        with ForkedFake.from_client(client):
 
149
        with fake_grackle_service(client):
236
150
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
237
151
            with ExpectedException(Exception, 'wtf'):
238
152
                client.put_message('arch1', 'asdf',
246
160
 
247
161
    def assertMessageIDs(self, ids, messages):
248
162
        self.assertIDOrder(
249
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
163
            sorted(ids), sorted(messages, key=lambda m:m['message_id']))
250
164
 
251
165
    def test_get_messages(self):
252
166
        client = GrackleClient('localhost', 8435)
253
 
        with ForkedFake.from_client(client,
 
167
        with fake_grackle_service(client,
254
168
            {'baz':
255
169
            [{'message_id': 'foo'},
256
170
             {'message_id': 'bar'}]}):
262
176
 
263
177
    def test_get_messages_by_id(self):
264
178
        client = GrackleClient('localhost', 8437)
265
 
        with ForkedFake.from_client(client,
 
179
        with fake_grackle_service(client,
266
180
            {'baz':
267
181
            [{'message_id': 'foo'},
268
182
             {'message_id': 'bar'}]}):
272
186
 
273
187
    def test_get_messages_batching(self):
274
188
        client = GrackleClient('localhost', 8438)
275
 
        with ForkedFake.from_client(client,
 
189
        with fake_grackle_service(client,
276
190
            {'baz':
277
191
            [{'message_id': 'foo'},
278
192
             {'message_id': 'bar'}]}):
287
201
 
288
202
    def get_messages_member_order_test(self, key):
289
203
        client = GrackleClient('localhost', 8439)
290
 
        with ForkedFake.from_client(client,
 
204
        with fake_grackle_service(client,
291
205
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
292
206
                 {'message_id': 'bar', key: '2011-03-24'}]}):
293
207
            response = client.get_messages('baz')
306
220
 
307
221
    def test_get_messages_thread_subject_order(self):
308
222
        client = GrackleClient('localhost', 8439)
309
 
        with ForkedFake.from_client(client, {'baz': [
 
223
        with fake_grackle_service(client, {'baz': [
310
224
            {'message_id': 'bar', 'subject': 'y'},
311
225
            {'message_id': 'qux', 'subject': 'z'},
312
226
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
320
234
 
321
235
    def test_get_messages_thread_oldest_order(self):
322
236
        client = GrackleClient('localhost', 8439)
323
 
        with ForkedFake.from_client(client, {'baz': [
 
237
        with fake_grackle_service(client, {'baz': [
324
238
            {'message_id': 'bar', 'date': 'x'},
325
239
            {'message_id': 'qux', 'date': 'z'},
326
240
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
334
248
 
335
249
    def test_get_messages_thread_newest_order(self):
336
250
        client = GrackleClient('localhost', 8439)
337
 
        with ForkedFake.from_client(client, {'baz': [
 
251
        with fake_grackle_service(client, {'baz': [
338
252
            {'message_id': 'bar', 'date': 'x'},
339
253
            {'message_id': 'qux', 'date': 'w'},
340
254
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
349
263
 
350
264
    def test_get_messages_unsupported_order(self):
351
265
        client = GrackleClient('localhost', 8439)
352
 
        with ForkedFake.from_client(client,
 
266
        with fake_grackle_service(client,
353
267
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
354
268
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
355
 
            with ExpectedException(UnsupportedOrder, ''):
 
269
            with ExpectedException(UnsupportedOrder):
356
270
                client.get_messages('baz', order='nonsense')
357
 
 
358
 
    def test_get_messages_headers_no_headers(self):
359
 
        client = GrackleClient('localhost', 8440)
360
 
        with ForkedFake.from_client(client,
361
 
            {'baz': [
362
 
                {'message_id': 'foo'}
363
 
            ]}):
364
 
            response = client.get_messages('baz', headers=[
365
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
366
 
        first_message = response['messages'][0]
367
 
        self.assertEqual('foo', first_message['message_id'])
368
 
        self.assertEqual({}, first_message['headers'])
369
 
 
370
 
    def test_get_messages_headers_exclude_headers(self):
371
 
        client = GrackleClient('localhost', 8441)
372
 
        with ForkedFake.from_client(client,
373
 
            {'baz': [
374
 
                {'message_id': 'foo', 'headers': {'From': 'me'}}
375
 
            ]}):
376
 
            response = client.get_messages('baz', headers=[
377
 
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
378
 
        first_message = response['messages'][0]
379
 
        self.assertEqual('foo', first_message['message_id'])
380
 
        self.assertEqual({}, first_message['headers'])
381
 
 
382
 
    def test_get_messages_headers_include_headers(self):
383
 
        client = GrackleClient('localhost', 8442)
384
 
        with ForkedFake.from_client(client,
385
 
            {'baz': [
386
 
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
387
 
            ]}):
388
 
            response = client.get_messages('baz', headers=[
389
 
                'From', 'To'])
390
 
        first_message = response['messages'][0]
391
 
        self.assertEqual('foo', first_message['message_id'])
392
 
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
393
 
 
394
 
    def test_get_messages_max_body_length(self):
395
 
        client = GrackleClient('localhost', 8443)
396
 
        with ForkedFake.from_client(client,
397
 
            {'baz': [
398
 
                {'message_id': 'foo', 'body': u'abcdefghi'}
399
 
            ]}):
400
 
            response = client.get_messages('baz', max_body_length=3)
401
 
        first_message = response['messages'][0]
402
 
        self.assertEqual('abc', first_message['body'])
403
 
 
404
 
    def test_include_hidden(self):
405
 
        client = GrackleClient('localhost', 8444)
406
 
        with ForkedFake.from_client(client,
407
 
            {'baz': [
408
 
                {'message_id': 'foo', 'hidden': True},
409
 
                {'message_id': 'bar', 'hidden': False}
410
 
            ]}):
411
 
            response = client.get_messages('baz', include_hidden=True)
412
 
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
413
 
            response = client.get_messages('baz', include_hidden=False)
414
 
            self.assertMessageIDs(['bar'], response['messages'])