60
58
return make_message(message_id, message.get_payload(), headers, hidden)
61
def threaded_messages(messages):
65
for message in messages:
66
if message.get('replies') is None:
67
threads[message['message_id']] = [message]
70
pending.append(message)
71
for message in pending:
72
threads[message['replies']].append(message)
73
return threads.values()
77
"""A memory-backed message store."""
79
def __init__(self, messages):
81
self.messages = messages
84
def is_multipart(message):
85
return isinstance(message['body'], list)
87
def get_messages(self, archive_id, query_string):
88
"""Return matching messages.
90
:param archive_id: The archive to retrieve from.
91
:param query_string: Contains 'parameters', which is a JSON-format
92
string describing parameters.
94
query = parse_qs(query_string)
95
parameters = simplejson.loads(query['parameters'][0])
96
order = parameters.get('order')
97
messages = self.messages[archive_id]
99
if order not in SUPPORTED_ORDERS:
100
raise UnsupportedOrder
101
elif order.startswith('thread_'):
102
threaded = threaded_messages(messages)
104
if order == 'thread_subject':
105
threaded.sort(key=lambda t: t[0]['subject'])
106
if order == 'thread_oldest':
107
threaded.sort(key=lambda t: min(m['date'] for m in t))
108
if order == 'thread_newest':
109
threaded.sort(key=lambda t: max(m['date'] for m in t))
110
for thread in threaded:
111
messages.extend(thread)
113
messages.sort(key=lambda m: m[order])
114
display_type = parameters.get('display_type', 'all')
115
if display_type not in SUPPORTED_DISPLAY_TYPES:
116
raise UnsupportedDisplayType
117
if 'date_range' in parameters:
119
start_date, end_date = parameters['date_range'].split('..')
121
raise UnparsableDateRange
123
for message in messages:
124
if (not parameters['include_hidden'] and message['hidden']):
126
if ('message_ids' in parameters
127
and message['message_id'] not in parameters['message_ids']):
129
if ('date_range' in parameters
130
and (message['date'] < start_date
131
or message['date'] > end_date)):
133
message = dict(message)
134
if 'headers' in parameters:
136
(k, v) for k, v in message['headers'].iteritems()
137
if k in parameters['headers'])
138
message['headers'] = headers
139
if display_type == 'headers-only':
141
elif display_type == 'text-only' and self.is_multipart(message):
143
part.get_payload() for part in message['body']
144
if part.get_content_type() == 'text/plain']
145
message['body'] = '\n\n'.join(text_parts)
146
elif display_type == 'all' and self.is_multipart(message):
147
parts = [str(part.get_payload()) for part in message['body']]
148
message['body'] = '\n\n'.join(parts)
149
max_body = parameters.get('max_body_length')
150
if max_body is not None and display_type != 'headers-only':
151
message['body'] = message['body'][:max_body]
152
new_messages.append(message)
153
messages = new_messages
154
limit = parameters.get('limit', 100)
155
memo = parameters.get('memo')
156
message_id_indices = dict(
157
(m['message_id'], idx) for idx, m in enumerate(messages))
161
start = message_id_indices[memo.encode('rot13')]
163
previous_memo = messages[start - 1]['message_id'].encode('rot13')
166
end = min(start + limit, len(messages))
167
if end < len(messages):
168
next_memo = messages[end]['message_id'].encode('rot13')
171
messages = messages[start:end]
174
'messages': messages,
175
'next_memo': next_memo,
176
'previous_memo': previous_memo
63
181
class ForkedFakeService:
64
182
"""A Grackle service fake, as a ContextManager."""
66
def __init__(self, port, message_archives=None, write_logs=False):
184
def __init__(self, port, messages=None, write_logs=False):
69
187
:param port: The tcp port to use.
70
:param message_archives: A dict of lists of dicts representing
71
archives of messages. The outer dict represents the archive,
72
the list represents the list of messages for that archive.
188
:param messages: A dict of lists of dicts representing messages. The
189
outer dict represents the archive, the list represents the list of
190
messages for that archive.
73
191
:param write_logs: If true, log messages will be written to stdout.
77
if message_archives is None:
78
self.message_archives = {}
80
self.message_archives = message_archives
198
self.messages = messages
81
199
self.read_end, self.write_end = os.pipe()
82
200
self.write_logs = write_logs
85
def from_client(client, message_archives=None):
203
def from_client(client, messages=None):
86
204
"""Instantiate a ForkedFakeService from the client.
88
206
:param port: The client to provide service for.
89
:param message_archives: A dict of lists of dicts representing
90
archives of messages. The outer dict represents the archive,
91
the list represents the list of messages for that archive.
207
:param messages: A dict of lists of dicts representing messages. The
208
outer dict represents the archive, the list represents the list of
209
messages for that archive.
93
return ForkedFakeService(client.port, message_archives)
211
return ForkedFakeService(client.port, messages)
95
213
def is_ready(self):
96
214
"""Tell the parent process that the server is ready for writes."""
437
563
self.assertEqual(['bar', 'naf', 'qux'], ids)
439
565
def test_date_range_unparsabledaterange(self):
440
client = GrackleClient('localhost', 8449)
566
client = GrackleClient('localhost', 8448)
441
567
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
442
568
with ForkedFakeService.from_client(client, archive):
443
569
with ExpectedException(UnparsableDateRange, ''):
444
570
client.get_messages('baz', date_range='2012-01-01')
446
def test_date_range_unparsabledaterange_missing_part(self):
447
client = GrackleClient('localhost', 8450)
448
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
449
with ForkedFakeService.from_client(client, archive):
450
with ExpectedException(UnparsableDateRange, ''):
451
client.get_messages('baz', date_range='2012-01-01..')
453
def test_date_range_unparsabledaterange_extra_part(self):
454
client = GrackleClient('localhost', 8451)
455
archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
456
with ForkedFakeService.from_client(client, archive):
457
with ExpectedException(UnparsableDateRange, ''):
458
client.get_messages('baz', date_range='2012-01..12-02..12-03')