34
36
return threads.values()
40
"""A memory-backed message store."""
39
def __init__(self, port, messages=None):
42
def __init__(self, messages):
42
44
self.messages = messages
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]
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.
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')
59
raise UnsupportedOrder
95
60
elif order.startswith('thread_'):
96
61
threaded = threaded_messages(messages)
105
70
messages.extend(thread)
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)
74
for message in messages:
76
not parameters['include_hidden']
77
and message.get('hidden', False)):
80
if ('message_ids' in parameters and
81
message['message_id'] not in parameters['message_ids']):
83
message = dict(message)
84
if 'headers' in parameters:
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(
130
111
messages = messages[start:end]
132
114
'messages': messages,
133
115
'next_memo': next_memo,
134
116
'previous_memo': previous_memo
136
self.wfile.write(simplejson.dumps(response))
139
def fake_grackle_service(client, messages=None):
142
return ForkedFake(client.port, messages)
123
"""A Grackle service fake, as a ContextManager."""
125
def __init__(self, port, messages=None, write_logs=False):
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.
138
self.messages = messages
139
self.read_end, self.write_end = os.pipe()
140
self.write_logs = write_logs
143
def from_client(client, messages=None):
144
"""Instantiate a ForkedFake from the client.
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.
151
return ForkedFake(client.port, messages)
154
"""Tell the parent process that the server is ready for writes."""
155
os.write(self.write_end, 'asdf')
160
Fork and start a server in the child. Return when the server is ready
166
os.read(self.read_end, 1)
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', {})
179
stream=sys.stderr, level=logging.INFO)
180
service.serve_forever()
182
def __exit__(self, exc_type, exc_val, traceback):
183
os.kill(self.pid, SIGKILL)
186
SUPPORTED_ORDERS = set(
187
['date', 'author', 'subject', 'thread_newest', 'thread_oldest',
191
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
192
"""A request handler that forwards to server.store."""
194
def __init__(self, *args, **kwargs):
195
"""Constructor. Sets up logging."""
196
self.logger = logging.getLogger('http')
197
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
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)
207
self.send_error(httplib.BAD_REQUEST)
210
"""Retrieve a list of messages on GET."""
211
scheme, netloc, path, params, query_string, fragments = (
213
parts = path.split('/')
214
if parts[1] == 'archive':
216
response = self.server.store.get_messages(
217
parts[2], query_string)
218
self.send_response(httplib.OK)
220
self.wfile.write(simplejson.dumps(response))
221
except UnsupportedOrder:
222
self.send_response(httplib.BAD_REQUEST)
223
self.wfile.write('Unsupported order')
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)
145
233
class TestPutMessage(TestCase):
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',
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')
360
def test_get_messages_headers_no_headers(self):
361
client = GrackleClient('localhost', 8440)
362
with ForkedFake.from_client(client,
364
{'message_id': 'foo'}
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'])
372
def test_get_messages_headers_exclude_headers(self):
373
client = GrackleClient('localhost', 8441)
374
with ForkedFake.from_client(client,
376
{'message_id': 'foo', 'headers': {'From': 'me'}}
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'])
384
def test_get_messages_headers_include_headers(self):
385
client = GrackleClient('localhost', 8442)
386
with ForkedFake.from_client(client,
388
{'message_id': 'foo', 'headers': {'From': 'me', 'To': 'you'}}
390
response = client.get_messages('baz', headers=[
392
first_message = response['messages'][0]
393
self.assertEqual('foo', first_message['message_id'])
394
self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
396
def test_get_messages_max_body_length(self):
397
client = GrackleClient('localhost', 8443)
398
with ForkedFake.from_client(client,
400
{'message_id': 'foo', 'body': u'abcdefghi'}
402
response = client.get_messages('baz', max_body_length=3)
403
first_message = response['messages'][0]
404
self.assertEqual('abc', first_message['body'])
406
def test_include_hidden(self):
407
client = GrackleClient('localhost', 8444)
408
with ForkedFake.from_client(client,
410
{'message_id': 'foo', 'hidden': True},
411
{'message_id': 'bar', 'hidden': False}
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'])