1
from BaseHTTPServer import (
3
BaseHTTPRequestHandler,
3
5
from email.message import Message
4
6
from email.mime.multipart import MIMEMultipart
5
7
from email.mime.text import MIMEText
11
from signal import SIGKILL
6
13
from StringIO import StringIO
7
15
from unittest import TestCase
16
from urlparse import urlparse
9
18
from testtools import ExpectedException
11
from grackle.client import GrackleClient
12
from grackle.error import (
20
from grackle.client import (
14
22
UnparsableDateRange,
15
23
UnsupportedDisplayType,
18
from grackle.service import ForkedFakeService
19
from grackle.store import make_json_message
26
from grackle.store import (
22
31
def make_message(message_id, body='body', headers=None, hidden=False):
23
32
if headers is None:
26
'Message-Id': message_id,
34
headers['Message-Id'] = message_id
36
'message_id': message_id,
38
'thread_id': message_id,
39
'date': headers.get('date', '2005-01-01'),
40
'subject': headers.get('subject', 'subject'),
41
'author': headers.get('author', 'author'),
44
'replies': headers.get('in-reply-to', None),
32
message_headers.update(headers.items())
34
message.set_payload(body)
35
for key, value in message_headers.items():
37
return make_json_message(message_id, message.as_string(), hidden)
40
50
def make_mime_message(message_id, body='body', headers=None, hidden=False,
41
51
attachment_type=None):
42
parts = MIMEMultipart()
43
parts.attach(MIMEText(body))
52
message = MIMEMultipart()
53
message.attach(MIMEText(body))
44
54
if attachment_type is not None:
45
55
attachment = Message()
46
56
attachment.set_payload('attactment data.')
47
57
attachment['Content-Type'] = attachment_type
48
58
attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
49
parts.attach(attachment)
50
return make_message(message_id, parts.as_string(), headers, hidden)
53
class TestPutArchive(TestCase):
55
def test_put_archive(self):
56
client = GrackleClient('localhost', 8410)
58
with ForkedFakeService.from_client(client, message_archives):
59
client.put_archive('arch1')
60
response = client.get_messages('arch1')
61
self.assertEqual(0, len(response['messages']))
63
def test_put_archive_existing_archive(self):
64
client = GrackleClient('localhost', 8411)
65
message_archives = {'arch1': []}
66
with ForkedFakeService.from_client(client, message_archives):
67
with ExpectedException(ArchiveIdExists, ''):
68
client.put_archive('arch1')
59
message.attach(attachment)
60
return make_message(message_id, message.get_payload(), headers, hidden)
63
class ForkedFakeService:
64
"""A Grackle service fake, as a ContextManager."""
66
def __init__(self, port, message_archives=None, write_logs=False):
69
: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.
73
: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
81
self.read_end, self.write_end = os.pipe()
82
self.write_logs = write_logs
85
def from_client(client, message_archives=None):
86
"""Instantiate a ForkedFakeService from the client.
88
: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.
93
return ForkedFakeService(client.port, message_archives)
96
"""Tell the parent process that the server is ready for writes."""
97
os.write(self.write_end, 'asdf')
102
Fork and start a server in the child. Return when the server is ready
108
os.read(self.read_end, 1)
111
def start_server(self):
112
"""Start the HTTP server."""
113
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
114
service.store = MemoryStore(self.message_archives)
115
for archive_id, messages in service.store.message_archives.iteritems():
116
for message in messages:
117
message.setdefault('headers', {})
121
stream=sys.stderr, level=logging.INFO)
122
service.serve_forever()
124
def __exit__(self, exc_type, exc_val, traceback):
125
os.kill(self.pid, SIGKILL)
128
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
129
"""A request handler that forwards to server.store."""
131
def __init__(self, *args, **kwargs):
132
"""Constructor. Sets up logging."""
133
self.logger = logging.getLogger('http')
134
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
137
"""Create a message on POST."""
138
message = self.rfile.read(int(self.headers['content-length']))
139
scheme, netloc, path, params, query_string, fragments = (
141
parts = path.split('/')
142
if parts[1] == 'archive':
143
# This expected path is /archive/archive_id/message_id.
144
self.server.store.put_message(parts[2], parts[3], message)
145
if message == 'This is a message':
146
self.send_response(httplib.CREATED)
150
self.send_error(httplib.BAD_REQUEST)
153
"""Retrieve a list of messages on GET."""
154
scheme, netloc, path, params, query_string, fragments = (
156
parts = path.split('/')
157
if parts[1] == 'archive':
159
response = self.server.store.get_messages(
160
parts[2], query_string)
161
self.send_response(httplib.OK)
163
self.wfile.write(simplejson.dumps(response))
164
except Exception, error:
166
httplib.BAD_REQUEST, error.__doc__)
169
def log_message(self, format, *args):
170
"""Override log_message to use standard Python logging."""
171
message = "%s - - [%s] %s\n" % (
172
self.address_string(), self.log_date_time_string(), format % args)
173
self.logger.info(message)
71
176
class TestPutMessage(TestCase):
73
178
def test_put_message(self):
74
179
client = GrackleClient('localhost', 8420)
75
180
message_archives = {'arch1': []}
76
with ForkedFakeService.from_client(client, message_archives):
181
service = ForkedFakeService.from_client(client, message_archives)
77
183
client.put_message('arch1', 'id1', StringIO('This is a message'))
78
184
response = client.get_messages('arch1')
79
self.assertEqual(1, len(response['messages']))
80
message = response['messages'][0]
81
self.assertEqual('id1', message['message_id'])
185
self.assertEqual(1, len(response['messages']))
186
message = response['messages'][0]
187
self.assertEqual('id1', message['message_id'])
83
def test_put_message_without_archive(self):
189
def test_put_message_without_message(self):
84
190
client = GrackleClient('localhost', 8421)
85
191
message_archives = {'arch1': []}
86
192
with ForkedFakeService.from_client(client, message_archives):
87
193
with ExpectedException(Exception, 'wtf'):
88
client.put_message('no-archive', 'id1', StringIO('message'))
194
client.put_message('arch1', 'id1',
195
StringIO('This is not a message'))
91
198
class TestGetMessages(TestCase):
368
473
with ForkedFakeService.from_client(client, archive):
369
474
with ExpectedException(UnparsableDateRange, ''):
370
475
client.get_messages('baz', date_range='2012-01..12-02..12-03')
373
class TestHideMessages(TestCase):
375
def test_hide_message_true(self):
376
client = GrackleClient('localhost', 8470)
379
make_message('foo', hidden=False),
381
with ForkedFakeService.from_client(client, archive):
382
response = client.hide_message('baz', 'foo', hidden=True)
383
self.assertEqual('foo', response['message_id'])
384
self.assertIs(True, response['hidden'])
386
def test_hide_message_false(self):
387
client = GrackleClient('localhost', 8470)
390
make_message('foo', hidden=True),
392
with ForkedFakeService.from_client(client, archive):
393
response = client.hide_message('baz', 'foo', hidden=False)
394
self.assertEqual('foo', response['message_id'])
395
self.assertIs(False, response['hidden'])