~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-02-29 23:55:28 UTC
  • Revision ID: curtis.hovey@canonical.com-20120229235528-7wphq02b2y9vt7ni
Implemented a partial put into the MemoryStore.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
__metaclass__ = type
2
 
 
 
1
from BaseHTTPServer import (
 
2
    HTTPServer,
 
3
    BaseHTTPRequestHandler,
 
4
    )
3
5
from email.message import Message
4
6
from email.mime.multipart import MIMEMultipart
5
7
from email.mime.text import MIMEText
 
8
import httplib
 
9
import logging
 
10
import os
 
11
from signal import SIGKILL
 
12
import simplejson
6
13
from StringIO import StringIO
 
14
import sys
7
15
from unittest import TestCase
 
16
from urlparse import urlparse
8
17
 
9
18
from testtools import ExpectedException
10
19
 
11
 
from grackle.client import GrackleClient
12
 
from grackle.error import (
13
 
    ArchiveIdExists,
 
20
from grackle.client import (
 
21
    GrackleClient,
14
22
    UnparsableDateRange,
15
23
    UnsupportedDisplayType,
16
24
    UnsupportedOrder,
17
25
    )
18
 
from grackle.service import ForkedFakeService
19
 
from grackle.store import make_json_message
 
26
from grackle.store import (
 
27
    MemoryStore,
 
28
    )
20
29
 
21
30
 
22
31
def make_message(message_id, body='body', headers=None, hidden=False):
23
32
    if headers is None:
24
33
        headers = {}
25
 
    message_headers = {
26
 
        'Message-Id': message_id,
27
 
        'date': '2005-01-01',
28
 
        'subject': 'subject',
29
 
        'from': 'author',
30
 
        'replies': '',
 
34
    headers['Message-Id'] = message_id
 
35
    message = {
 
36
        'message_id': message_id,
 
37
        'headers': headers,
 
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'),
 
42
        'hidden': hidden,
 
43
        'attachments': [],
 
44
        'replies': headers.get('in-reply-to', None),
 
45
        'body': body,
31
46
        }
32
 
    message_headers.update(headers.items())
33
 
    message = Message()
34
 
    message.set_payload(body)
35
 
    for key, value in message_headers.items():
36
 
        message[key] = value
37
 
    return make_json_message(message_id, message.as_string(), hidden)
 
47
    return message
38
48
 
39
49
 
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)
51
 
 
52
 
 
53
 
class TestPutArchive(TestCase):
54
 
 
55
 
    def test_put_archive(self):
56
 
        client = GrackleClient('localhost', 8410)
57
 
        message_archives = {}
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']))
62
 
 
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)
 
61
 
 
62
 
 
63
class ForkedFakeService:
 
64
    """A Grackle service fake, as a ContextManager."""
 
65
 
 
66
    def __init__(self, port, message_archives=None, write_logs=False):
 
67
        """Constructor.
 
68
 
 
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.
 
74
        """
 
75
        self.pid = None
 
76
        self.port = port
 
77
        if message_archives is None:
 
78
            self.message_archives = {}
 
79
        else:
 
80
            self.message_archives = message_archives
 
81
        self.read_end, self.write_end = os.pipe()
 
82
        self.write_logs = write_logs
 
83
 
 
84
    @staticmethod
 
85
    def from_client(client, message_archives=None):
 
86
        """Instantiate a ForkedFakeService from the client.
 
87
 
 
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.
 
92
        """
 
93
        return ForkedFakeService(client.port, message_archives)
 
94
 
 
95
    def is_ready(self):
 
96
        """Tell the parent process that the server is ready for writes."""
 
97
        os.write(self.write_end, 'asdf')
 
98
 
 
99
    def __enter__(self):
 
100
        """Run the service.
 
101
 
 
102
        Fork and start a server in the child.  Return when the server is ready
 
103
        for use."""
 
104
        pid = os.fork()
 
105
        if pid == 0:
 
106
            self.start_server()
 
107
        self.pid = pid
 
108
        os.read(self.read_end, 1)
 
109
        return
 
110
 
 
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', {})
 
118
        self.is_ready()
 
119
        if self.write_logs:
 
120
            logging.basicConfig(
 
121
                stream=sys.stderr, level=logging.INFO)
 
122
        service.serve_forever()
 
123
 
 
124
    def __exit__(self, exc_type, exc_val, traceback):
 
125
        os.kill(self.pid, SIGKILL)
 
126
 
 
127
 
 
128
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
 
129
    """A request handler that forwards to server.store."""
 
130
 
 
131
    def __init__(self, *args, **kwargs):
 
132
        """Constructor.  Sets up logging."""
 
133
        self.logger = logging.getLogger('http')
 
134
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
 
135
 
 
136
    def do_POST(self):
 
137
        """Create a message on POST."""
 
138
        message = self.rfile.read(int(self.headers['content-length']))
 
139
        scheme, netloc, path, params, query_string, fragments = (
 
140
            urlparse(self.path))
 
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)
 
147
            self.end_headers()
 
148
            self.wfile.close()
 
149
        else:
 
150
            self.send_error(httplib.BAD_REQUEST)
 
151
 
 
152
    def do_GET(self):
 
153
        """Retrieve a list of messages on GET."""
 
154
        scheme, netloc, path, params, query_string, fragments = (
 
155
            urlparse(self.path))
 
156
        parts = path.split('/')
 
157
        if parts[1] == 'archive':
 
158
            try:
 
159
                response = self.server.store.get_messages(
 
160
                    parts[2], query_string)
 
161
                self.send_response(httplib.OK)
 
162
                self.end_headers()
 
163
                self.wfile.write(simplejson.dumps(response))
 
164
            except Exception, error:
 
165
                self.send_response(
 
166
                    httplib.BAD_REQUEST, error.__doc__)
 
167
                return
 
168
 
 
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)
69
174
 
70
175
 
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)
 
182
        with service:
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'])
82
188
 
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'))
89
196
 
90
197
 
91
198
class TestGetMessages(TestCase):
132
239
 
133
240
    def get_messages_member_order_test(self, key):
134
241
        client = GrackleClient('localhost', 8439)
135
 
        if key == 'author':
136
 
            header_name = 'from'
137
 
        else:
138
 
            header_name = key
139
242
        archive = {
140
243
            'baz': [
141
 
                make_message('foo', headers={header_name: '2011-03-25'}),
142
 
                make_message('bar', headers={header_name: '2011-03-24'}),
 
244
                make_message('foo', headers={key: '2011-03-25'}),
 
245
                make_message('bar', headers={key: '2011-03-24'}),
143
246
             ]}
144
247
        with ForkedFakeService.from_client(client, archive):
145
248
            response = client.get_messages('baz')
292
395
        first_message = response['messages'][0]
293
396
        self.assertEqual('foo', first_message['message_id'])
294
397
        self.assertEqual(
295
 
            archive['baz'][0]['headers'], first_message['headers'])
 
398
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
 
399
            first_message['headers'])
296
400
        self.assertNotIn('body', first_message)
297
401
 
298
402
    def test_display_type_text_only(self):
309
413
        self.assertEqual('foo', first_message['message_id'])
310
414
        self.assertEqual('me', first_message['headers']['From'])
311
415
        self.assertEqual('you', first_message['headers']['To'])
312
 
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
416
        self.assertEqual('abcdefghi', first_message['body'])
313
417
 
314
418
    def test_display_type_all(self):
315
419
        client = GrackleClient('localhost', 8447)
325
429
        self.assertEqual('foo', first_message['message_id'])
326
430
        self.assertEqual('me', first_message['headers']['From'])
327
431
        self.assertEqual('you', first_message['headers']['To'])
328
 
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
432
        self.assertEqual(
 
433
            'abcdefghi\n\nattactment data.', first_message['body'])
329
434
 
330
435
    def test_date_range(self):
331
436
        client = GrackleClient('localhost', 8448)
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')
371
 
 
372
 
 
373
 
class TestHideMessages(TestCase):
374
 
 
375
 
    def test_hide_message_true(self):
376
 
        client = GrackleClient('localhost', 8470)
377
 
        archive = {
378
 
            'baz': [
379
 
                make_message('foo', hidden=False),
380
 
            ]}
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'])
385
 
 
386
 
    def test_hide_message_false(self):
387
 
        client = GrackleClient('localhost', 8470)
388
 
        archive = {
389
 
            'baz': [
390
 
                make_message('foo', hidden=True),
391
 
            ]}
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'])