~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 14:30:09 UTC
  • Revision ID: aaron@canonical.com-20120113143009-t0kzaz3czxnv1nwz
get_messagesĀ supportsĀ headerĀ parameter.

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."""
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
 
        """
 
37
class ForkedFake:
 
38
 
 
39
    def __init__(self, port, messages=None):
132
40
        self.pid = None
133
41
        self.port = port
134
 
        if messages is None:
135
 
            self.messages = {}
136
 
        else:
137
 
            self.messages = messages
 
42
        self.messages = messages
138
43
        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
44
 
152
45
    def is_ready(self):
153
 
        """Tell the parent process that the server is ready for writes."""
154
46
        os.write(self.write_end, 'asdf')
155
47
 
156
48
    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
49
        pid = os.fork()
162
50
        if pid == 0:
163
51
            self.start_server()
166
54
        return
167
55
 
168
56
    def start_server(self):
169
 
        """Start the HTTP server."""
170
57
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
171
 
        service.store = GrackleStore(self.messages)
172
 
        for archive_id, messages in service.store.messages.iteritems():
 
58
        service.messages = self.messages
 
59
        for archive_id, messages in service.messages.iteritems():
173
60
            for message in messages:
174
61
                message.setdefault('headers', {})
175
62
        self.is_ready()
176
 
        if self.write_logs:
177
 
            logging.basicConfig(
178
 
                stream=sys.stderr, level=logging.INFO)
179
63
        service.serve_forever()
180
64
 
181
65
    def __exit__(self, exc_type, exc_val, traceback):
188
72
 
189
73
 
190
74
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
75
 
198
76
    def do_POST(self):
199
 
        """Create a message on POST."""
200
77
        message = self.rfile.read(int(self.headers['content-length']))
201
78
        if message == 'This is a message':
202
79
            self.send_response(httplib.CREATED)
206
83
            self.send_error(httplib.BAD_REQUEST)
207
84
 
208
85
    def do_GET(self):
209
 
        """Retrieve a list of messages on GET."""
210
86
        scheme, netloc, path, params, query_string, fragments = (
211
87
            urlparse(self.path))
212
88
        parts = path.split('/')
213
89
        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:
 
90
            self.get_messages(parts[2], query_string)
 
91
 
 
92
    def get_messages(self, archive_id, query_string):
 
93
        query = parse_qs(query_string)
 
94
        parameters = simplejson.loads(query['parameters'][0])
 
95
        order = parameters.get('order')
 
96
        messages = self.server.messages[archive_id]
 
97
        if order is not None :
 
98
            if order not in SUPPORTED_ORDERS:
221
99
                self.send_response(httplib.BAD_REQUEST)
222
100
                self.wfile.write('Unsupported order')
223
101
                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)
 
102
            elif order.startswith('thread_'):
 
103
                threaded = threaded_messages(messages)
 
104
                messages = []
 
105
                if order == 'thread_subject':
 
106
                    threaded.sort(key=lambda t: t[0]['subject'])
 
107
                if order == 'thread_oldest':
 
108
                    threaded.sort(key=lambda t: min(m['date'] for m in t))
 
109
                if order == 'thread_newest':
 
110
                    threaded.sort(key=lambda t: max(m['date'] for m in t))
 
111
                for thread in threaded:
 
112
                    messages.extend(thread)
 
113
            else:
 
114
                messages.sort(key=lambda m: m[order])
 
115
        messages = [m for m in messages
 
116
                    if 'message_ids' not in parameters or
 
117
                    m['message_id'] in parameters['message_ids']]
 
118
        self.send_response(httplib.OK)
 
119
        self.end_headers()
 
120
        limit = parameters.get('limit', 100)
 
121
        memo = parameters.get('memo')
 
122
        message_id_indices = dict(
 
123
            (m['message_id'], idx) for idx, m in enumerate(messages))
 
124
        if memo is None:
 
125
            start = 0
 
126
        else:
 
127
            start = message_id_indices[memo.encode('rot13')]
 
128
        if start > 0:
 
129
            previous_memo = messages[start - 1]['message_id'].encode('rot13')
 
130
        else:
 
131
            previous_memo = None
 
132
        end = min(start + limit, len(messages))
 
133
        if end < len(messages):
 
134
            next_memo = messages[end]['message_id'].encode('rot13')
 
135
        else:
 
136
            next_memo = None
 
137
        messages = messages[start:end]
 
138
        new_messages = []
 
139
        for message in messages:
 
140
            message = dict(message)
 
141
            if 'headers' in parameters:
 
142
                headers = dict(
 
143
                    (k, v) for k, v in message['headers'].iteritems()
 
144
                    if k in parameters['headers'])
 
145
                message['headers'] = headers
 
146
            new_messages.append(message)
 
147
        response = {
 
148
            'messages': new_messages,
 
149
            'next_memo': next_memo,
 
150
            'previous_memo': previous_memo
 
151
            }
 
152
        self.wfile.write(simplejson.dumps(response))
 
153
 
 
154
 
 
155
def fake_grackle_service(client, messages=None):
 
156
    if messages is None:
 
157
        messages = {}
 
158
    return ForkedFake(client.port, messages)
230
159
 
231
160
 
232
161
class TestPutMessage(TestCase):
233
162
 
234
163
    def test_put_message(self):
235
164
        client = GrackleClient('localhost', 8436)
236
 
        with ForkedFakeService.from_client(client):
 
165
        with fake_grackle_service(client):
237
166
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
238
167
            with ExpectedException(Exception, 'wtf'):
239
168
                client.put_message('arch1', 'asdf',
247
176
 
248
177
    def assertMessageIDs(self, ids, messages):
249
178
        self.assertIDOrder(
250
 
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
179
            sorted(ids), sorted(messages, key=lambda m:m['message_id']))
251
180
 
252
181
    def test_get_messages(self):
253
182
        client = GrackleClient('localhost', 8435)
254
 
        with ForkedFakeService.from_client(client,
 
183
        with fake_grackle_service(client,
255
184
            {'baz':
256
185
            [{'message_id': 'foo'},
257
186
             {'message_id': 'bar'}]}):
263
192
 
264
193
    def test_get_messages_by_id(self):
265
194
        client = GrackleClient('localhost', 8437)
266
 
        with ForkedFakeService.from_client(client,
 
195
        with fake_grackle_service(client,
267
196
            {'baz':
268
197
            [{'message_id': 'foo'},
269
198
             {'message_id': 'bar'}]}):
273
202
 
274
203
    def test_get_messages_batching(self):
275
204
        client = GrackleClient('localhost', 8438)
276
 
        with ForkedFakeService.from_client(client,
 
205
        with fake_grackle_service(client,
277
206
            {'baz':
278
207
            [{'message_id': 'foo'},
279
208
             {'message_id': 'bar'}]}):
288
217
 
289
218
    def get_messages_member_order_test(self, key):
290
219
        client = GrackleClient('localhost', 8439)
291
 
        with ForkedFakeService.from_client(client,
 
220
        with fake_grackle_service(client,
292
221
                {'baz': [{'message_id': 'foo', key: '2011-03-25'},
293
222
                 {'message_id': 'bar', key: '2011-03-24'}]}):
294
223
            response = client.get_messages('baz')
307
236
 
308
237
    def test_get_messages_thread_subject_order(self):
309
238
        client = GrackleClient('localhost', 8439)
310
 
        with ForkedFakeService.from_client(client, {'baz': [
 
239
        with fake_grackle_service(client, {'baz': [
311
240
            {'message_id': 'bar', 'subject': 'y'},
312
241
            {'message_id': 'qux', 'subject': 'z'},
313
242
            {'message_id': 'foo', 'subject': 'x', 'in_reply_to': 'qux'},
321
250
 
322
251
    def test_get_messages_thread_oldest_order(self):
323
252
        client = GrackleClient('localhost', 8439)
324
 
        with ForkedFakeService.from_client(client, {'baz': [
 
253
        with fake_grackle_service(client, {'baz': [
325
254
            {'message_id': 'bar', 'date': 'x'},
326
255
            {'message_id': 'qux', 'date': 'z'},
327
256
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'qux'},
335
264
 
336
265
    def test_get_messages_thread_newest_order(self):
337
266
        client = GrackleClient('localhost', 8439)
338
 
        with ForkedFakeService.from_client(client, {'baz': [
 
267
        with fake_grackle_service(client, {'baz': [
339
268
            {'message_id': 'bar', 'date': 'x'},
340
269
            {'message_id': 'qux', 'date': 'w'},
341
270
            {'message_id': 'foo', 'date': 'y', 'in_reply_to': 'bar'},
350
279
 
351
280
    def test_get_messages_unsupported_order(self):
352
281
        client = GrackleClient('localhost', 8439)
353
 
        with ForkedFakeService.from_client(client,
 
282
        with fake_grackle_service(client,
354
283
                {'baz': [{'message_id': 'foo', 'date': '2011-03-25'},
355
284
                 {'message_id': 'bar', 'date': '2011-03-24'}]}):
356
 
            with ExpectedException(UnsupportedOrder, ''):
 
285
            with ExpectedException(UnsupportedOrder):
357
286
                client.get_messages('baz', order='nonsense')
358
287
 
359
288
    def test_get_messages_headers_no_headers(self):
360
289
        client = GrackleClient('localhost', 8440)
361
 
        with ForkedFakeService.from_client(client,
 
290
        with fake_grackle_service(client,
362
291
            {'baz': [
363
292
                {'message_id': 'foo'}
364
293
            ]}):
369
298
        self.assertEqual({}, first_message['headers'])
370
299
 
371
300
    def test_get_messages_headers_exclude_headers(self):
372
 
        client = GrackleClient('localhost', 8441)
373
 
        with ForkedFakeService.from_client(client,
 
301
        client = GrackleClient('localhost', 8440)
 
302
        with fake_grackle_service(client,
374
303
            {'baz': [
375
304
                {'message_id': 'foo', 'headers': {'From': 'me'}}
376
305
            ]}):
381
310
        self.assertEqual({}, first_message['headers'])
382
311
 
383
312
    def test_get_messages_headers_include_headers(self):
384
 
        client = GrackleClient('localhost', 8442)
385
 
        with ForkedFakeService.from_client(client,
 
313
        client = GrackleClient('localhost', 8440)
 
314
        with fake_grackle_service(client,
386
315
            {'baz': [
387
316
                {'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
388
317
            ]}):
391
320
        first_message = response['messages'][0]
392
321
        self.assertEqual('foo', first_message['message_id'])
393
322
        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'])