36
34
return threads.values()
40
"""A memory-backed message store."""
42
def __init__(self, messages):
39
def __init__(self, port, messages=None):
44
42
self.messages = messages
46
def get_messages(self, archive_id, query_string):
47
"""Return matching messages.
49
:param archive_id: The archive to retrieve from.
50
:param query_string: Contains 'parameters', which is a JSON-format
51
string describing parameters.
43
self.read_end, self.write_end = os.pipe()
46
os.write(self.write_end, 'asdf')
53
os.read(self.read_end, 1)
56
def start_server(self):
57
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
58
service.messages = self.messages
60
service.serve_forever()
62
def __exit__(self, exc_type, exc_val, traceback):
63
os.kill(self.pid, SIGKILL)
66
SUPPORTED_ORDERS = set(
67
['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
71
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
74
message = self.rfile.read(int(self.headers['content-length']))
75
if message == 'This is a message':
76
self.send_response(httplib.CREATED)
80
self.send_error(httplib.BAD_REQUEST)
83
scheme, netloc, path, params, query_string, fragments = (
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]
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')
60
95
elif order.startswith('thread_'):
61
96
threaded = threaded_messages(messages)
70
105
messages.extend(thread)
72
107
messages.sort(key=lambda m: m[order])
74
for message in messages:
75
if (not parameters['include_hidden']
76
and message.get('hidden', False)):
79
if ('message_ids' in parameters
80
and message['message_id'] not in parameters['message_ids']):
82
message = dict(message)
83
if 'headers' in parameters:
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)
93
113
limit = parameters.get('limit', 100)
94
114
memo = parameters.get('memo')
95
115
message_id_indices = dict(
110
130
messages = messages[start:end]
113
132
'messages': messages,
114
133
'next_memo': next_memo,
115
134
'previous_memo': previous_memo
121
"""A Grackle service fake, as a ContextManager."""
123
def __init__(self, port, messages=None, write_logs=False):
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.
136
self.messages = messages
137
self.read_end, self.write_end = os.pipe()
138
self.write_logs = write_logs
141
def from_client(client, messages=None):
142
"""Instantiate a ForkedFake from the client.
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.
149
return ForkedFake(client.port, messages)
152
"""Tell the parent process that the server is ready for writes."""
153
os.write(self.write_end, 'asdf')
158
Fork and start a server in the child. Return when the server is ready
164
os.read(self.read_end, 1)
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', {})
177
stream=sys.stderr, level=logging.INFO)
178
service.serve_forever()
180
def __exit__(self, exc_type, exc_val, traceback):
181
os.kill(self.pid, SIGKILL)
184
SUPPORTED_ORDERS = set(
185
['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
189
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
190
"""A request handler that forwards to server.store."""
192
def __init__(self, *args, **kwargs):
193
"""Constructor. Sets up logging."""
194
self.logger = logging.getLogger('http')
195
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
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)
205
self.send_error(httplib.BAD_REQUEST)
208
"""Retrieve a list of messages on GET."""
209
scheme, netloc, path, params, query_string, fragments = (
211
parts = path.split('/')
212
if parts[1] == 'archive':
214
response = self.server.store.get_messages(
215
parts[2], query_string)
216
self.send_response(httplib.OK)
218
self.wfile.write(simplejson.dumps(response))
219
except UnsupportedOrder:
220
self.send_response(httplib.BAD_REQUEST)
221
self.wfile.write('Unsupported order')
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))
139
def fake_grackle_service(client, messages=None):
142
return ForkedFake(client.port, messages)
231
145
class TestPutMessage(TestCase):
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',
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')
358
def test_get_messages_headers_no_headers(self):
359
client = GrackleClient('localhost', 8440)
360
with ForkedFake.from_client(client,
362
{'message_id': 'foo'}
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'])
370
def test_get_messages_headers_exclude_headers(self):
371
client = GrackleClient('localhost', 8441)
372
with ForkedFake.from_client(client,
374
{'message_id': 'foo', 'headers': {'From': 'me'}}
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'])
382
def test_get_messages_headers_include_headers(self):
383
client = GrackleClient('localhost', 8442)
384
with ForkedFake.from_client(client,
386
{'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
388
response = client.get_messages('baz', headers=[
390
first_message = response['messages'][0]
391
self.assertEqual('foo', first_message['message_id'])
392
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
394
def test_get_messages_max_body_length(self):
395
client = GrackleClient('localhost', 8443)
396
with ForkedFake.from_client(client,
398
{'message_id': 'foo', 'body': u'abcdefghi'}
400
response = client.get_messages('baz', max_body_length=3)
401
first_message = response['messages'][0]
402
self.assertEqual('abc', first_message['body'])
404
def test_include_hidden(self):
405
client = GrackleClient('localhost', 8444)
406
with ForkedFake.from_client(client,
408
{'message_id': 'foo', 'hidden': True},
409
{'message_id': 'bar', 'hidden': False}
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'])