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
32
def make_message(message_id, body='body', headers=None, hidden=False):
23
33
if headers is None:
26
'Message-Id': message_id,
35
headers['Message-Id'] = message_id
37
'message_id': message_id,
39
'thread_id': message_id,
40
'date': headers.get('date', '2005-01-01'),
41
'subject': headers.get('subject', 'subject'),
42
'author': headers.get('author', 'author'),
45
'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
51
def make_mime_message(message_id, body='body', headers=None, hidden=False,
41
52
attachment_type=None):
42
55
parts = MIMEMultipart()
43
56
parts.attach(MIMEText(body))
44
57
if attachment_type is not None:
47
60
attachment['Content-Type'] = attachment_type
48
61
attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
49
62
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')
64
message.set_payload(parts.as_string())
65
for key, value in headers.items():
67
return make_json_message(message_id, message.as_string())
70
class ForkedFakeService:
71
"""A Grackle service fake, as a ContextManager."""
73
def __init__(self, port, message_archives=None, write_logs=False):
76
:param port: The tcp port to use.
77
:param message_archives: A dict of lists of dicts representing
78
archives of messages. The outer dict represents the archive,
79
the list represents the list of messages for that archive.
80
:param write_logs: If true, log messages will be written to stdout.
84
if message_archives is None:
85
self.message_archives = {}
87
self.message_archives = message_archives
88
self.read_end, self.write_end = os.pipe()
89
self.write_logs = write_logs
92
def from_client(client, message_archives=None):
93
"""Instantiate a ForkedFakeService from the client.
95
:param port: The client to provide service for.
96
:param message_archives: A dict of lists of dicts representing
97
archives of messages. The outer dict represents the archive,
98
the list represents the list of messages for that archive.
100
return ForkedFakeService(client.port, message_archives)
103
"""Tell the parent process that the server is ready for writes."""
104
os.write(self.write_end, 'asdf')
109
Fork and start a server in the child. Return when the server is ready
115
os.read(self.read_end, 1)
118
def start_server(self):
119
"""Start the HTTP server."""
120
service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
121
service.store = MemoryStore(self.message_archives)
122
for archive_id, messages in service.store.message_archives.iteritems():
123
for message in messages:
124
message.setdefault('headers', {})
128
stream=sys.stderr, level=logging.INFO)
129
service.serve_forever()
131
def __exit__(self, exc_type, exc_val, traceback):
132
os.kill(self.pid, SIGKILL)
135
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
136
"""A request handler that forwards to server.store."""
138
def __init__(self, *args, **kwargs):
139
"""Constructor. Sets up logging."""
140
self.logger = logging.getLogger('http')
141
BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
144
"""Create a message on POST."""
145
message = self.rfile.read(int(self.headers['content-length']))
146
scheme, netloc, path, params, query_string, fragments = (
148
parts = path.split('/')
149
if parts[1] == 'archive' and len(parts) == 4:
151
# This expected path is /archive/archive_id/message_id.
152
self.server.store.put_message(parts[2], parts[3], message)
153
self.send_response(httplib.CREATED)
157
self.send_error(httplib.BAD_REQUEST)
160
"""Retrieve a list of messages on GET."""
161
scheme, netloc, path, params, query_string, fragments = (
163
parts = path.split('/')
164
if parts[1] == 'archive':
166
response = self.server.store.get_messages(
167
parts[2], query_string)
168
self.send_response(httplib.OK)
170
self.wfile.write(simplejson.dumps(response))
171
except Exception, error:
173
httplib.BAD_REQUEST, error.__doc__)
176
def log_message(self, format, *args):
177
"""Override log_message to use standard Python logging."""
178
message = "%s - - [%s] %s\n" % (
179
self.address_string(), self.log_date_time_string(), format % args)
180
self.logger.info(message)
71
183
class TestPutMessage(TestCase):
368
477
with ForkedFakeService.from_client(client, archive):
369
478
with ExpectedException(UnparsableDateRange, ''):
370
479
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'])