~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-03-17 21:28:19 UTC
  • Revision ID: curtis.hovey@canonical.com-20120317212819-qb4izc3zxecemxso
Handle usupported URLS and methods.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
from BaseHTTPServer import (
2
 
    HTTPServer,
3
 
    BaseHTTPRequestHandler,
4
 
    )
5
1
from email.message import Message
6
2
from email.mime.multipart import MIMEMultipart
7
3
from email.mime.text import MIMEText
8
 
import httplib
9
 
import logging
10
 
import os
11
 
from signal import SIGKILL
12
 
import simplejson
13
4
from StringIO import StringIO
14
 
import sys
15
5
from unittest import TestCase
16
 
from urlparse import urlparse
17
6
 
18
7
from testtools import ExpectedException
19
8
 
20
 
from grackle.client import (
21
 
    GrackleClient,
 
9
from grackle.client import GrackleClient
 
10
from grackle.error import (
 
11
    ArchiveIdExists,
22
12
    UnparsableDateRange,
23
13
    UnsupportedDisplayType,
24
14
    UnsupportedOrder,
25
15
    )
 
16
from grackle.service import ForkedFakeService
26
17
from grackle.store import (
27
18
    make_json_message,
28
 
    MemoryStore,
29
19
    )
30
20
 
31
21
 
32
22
def make_message(message_id, body='body', headers=None, hidden=False):
33
23
    if headers is None:
34
24
        headers = {}
35
 
    headers['Message-Id'] = message_id
36
 
    message = {
37
 
        'message_id': message_id,
38
 
        'headers': headers,
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'),
43
 
        'hidden': hidden,
44
 
        'attachments': [],
45
 
        'replies': headers.get('in-reply-to', None),
46
 
        'body': body,
 
25
    message_headers = {
 
26
        'Message-Id': message_id,
 
27
        'date': '2005-01-01',
 
28
        'subject': 'subject',
 
29
        'from': 'author',
 
30
        'replies': '',
47
31
        }
48
 
    return message
 
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)
49
38
 
50
39
 
51
40
def make_mime_message(message_id, body='body', headers=None, hidden=False,
52
41
                      attachment_type=None):
53
 
    message = MIMEMultipart()
54
 
    message.attach(MIMEText(body))
55
 
    if headers is None:
56
 
        headers = {}
57
 
    for key, value in headers.items():
58
 
        message[key] = value
 
42
    parts = MIMEMultipart()
 
43
    parts.attach(MIMEText(body))
59
44
    if attachment_type is not None:
60
45
        attachment = Message()
61
46
        attachment.set_payload('attactment data.')
62
47
        attachment['Content-Type'] = attachment_type
63
48
        attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
64
 
        message.attach(attachment)
65
 
    return make_json_message(message_id, message.as_string())
66
 
 
67
 
 
68
 
class ForkedFakeService:
69
 
    """A Grackle service fake, as a ContextManager."""
70
 
 
71
 
    def __init__(self, port, message_archives=None, write_logs=False):
72
 
        """Constructor.
73
 
 
74
 
        :param port: The tcp port to use.
75
 
        :param message_archives: A dict of lists of dicts representing
76
 
            archives of messages. The outer dict represents the archive,
77
 
            the list represents the list of messages for that archive.
78
 
        :param write_logs: If true, log messages will be written to stdout.
79
 
        """
80
 
        self.pid = None
81
 
        self.port = port
82
 
        if message_archives is None:
83
 
            self.message_archives = {}
84
 
        else:
85
 
            self.message_archives = message_archives
86
 
        self.read_end, self.write_end = os.pipe()
87
 
        self.write_logs = write_logs
88
 
 
89
 
    @staticmethod
90
 
    def from_client(client, message_archives=None):
91
 
        """Instantiate a ForkedFakeService from the client.
92
 
 
93
 
        :param port: The client to provide service for.
94
 
        :param message_archives: A dict of lists of dicts representing
95
 
            archives of messages. The outer dict represents the archive,
96
 
            the list represents the list of messages for that archive.
97
 
        """
98
 
        return ForkedFakeService(client.port, message_archives)
99
 
 
100
 
    def is_ready(self):
101
 
        """Tell the parent process that the server is ready for writes."""
102
 
        os.write(self.write_end, 'asdf')
103
 
 
104
 
    def __enter__(self):
105
 
        """Run the service.
106
 
 
107
 
        Fork and start a server in the child.  Return when the server is ready
108
 
        for use."""
109
 
        pid = os.fork()
110
 
        if pid == 0:
111
 
            self.start_server()
112
 
        self.pid = pid
113
 
        os.read(self.read_end, 1)
114
 
        return
115
 
 
116
 
    def start_server(self):
117
 
        """Start the HTTP server."""
118
 
        service = HTTPServer(('', self.port), FakeGrackleRequestHandler)
119
 
        service.store = MemoryStore(self.message_archives)
120
 
        for archive_id, messages in service.store.message_archives.iteritems():
121
 
            for message in messages:
122
 
                message.setdefault('headers', {})
123
 
        self.is_ready()
124
 
        if self.write_logs:
125
 
            logging.basicConfig(
126
 
                stream=sys.stderr, level=logging.INFO)
127
 
        service.serve_forever()
128
 
 
129
 
    def __exit__(self, exc_type, exc_val, traceback):
130
 
        os.kill(self.pid, SIGKILL)
131
 
 
132
 
 
133
 
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
134
 
    """A request handler that forwards to server.store."""
135
 
 
136
 
    def __init__(self, *args, **kwargs):
137
 
        """Constructor.  Sets up logging."""
138
 
        self.logger = logging.getLogger('http')
139
 
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
140
 
 
141
 
    def do_POST(self):
142
 
        """Create a message on POST."""
143
 
        message = self.rfile.read(int(self.headers['content-length']))
144
 
        scheme, netloc, path, params, query_string, fragments = (
145
 
            urlparse(self.path))
146
 
        parts = path.split('/')
147
 
        if parts[1] == 'archive' and len(parts) == 4:
148
 
            try:
149
 
                # This expected path is /archive/archive_id/message_id.
150
 
                self.server.store.put_message(parts[2], parts[3], message)
151
 
                self.send_response(httplib.CREATED)
152
 
                self.end_headers()
153
 
                self.wfile.close()
154
 
            except:
155
 
                self.send_error(httplib.BAD_REQUEST)
156
 
 
157
 
    def do_GET(self):
158
 
        """Retrieve a list of messages on GET."""
159
 
        scheme, netloc, path, params, query_string, fragments = (
160
 
            urlparse(self.path))
161
 
        parts = path.split('/')
162
 
        if parts[1] == 'archive':
163
 
            try:
164
 
                response = self.server.store.get_messages(
165
 
                    parts[2], query_string)
166
 
                self.send_response(httplib.OK)
167
 
                self.end_headers()
168
 
                self.wfile.write(simplejson.dumps(response))
169
 
            except Exception, error:
170
 
                self.send_response(
171
 
                    httplib.BAD_REQUEST, error.__doc__)
172
 
                return
173
 
 
174
 
    def log_message(self, format, *args):
175
 
        """Override log_message to use standard Python logging."""
176
 
        message = "%s - - [%s] %s\n" % (
177
 
            self.address_string(), self.log_date_time_string(), format % args)
178
 
        self.logger.info(message)
 
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')
179
69
 
180
70
 
181
71
class TestPutMessage(TestCase):
242
132
 
243
133
    def get_messages_member_order_test(self, key):
244
134
        client = GrackleClient('localhost', 8439)
 
135
        if key == 'author':
 
136
            header_name = 'from'
 
137
        else:
 
138
            header_name = key
245
139
        archive = {
246
140
            'baz': [
247
 
                make_message('foo', headers={key: '2011-03-25'}),
248
 
                make_message('bar', headers={key: '2011-03-24'}),
 
141
                make_message('foo', headers={header_name: '2011-03-25'}),
 
142
                make_message('bar', headers={header_name: '2011-03-24'}),
249
143
             ]}
250
144
        with ForkedFakeService.from_client(client, archive):
251
145
            response = client.get_messages('baz')
398
292
        first_message = response['messages'][0]
399
293
        self.assertEqual('foo', first_message['message_id'])
400
294
        self.assertEqual(
401
 
            {'From': 'me', 'Message-Id': 'foo', 'To': 'you'},
402
 
            first_message['headers'])
 
295
            archive['baz'][0]['headers'], first_message['headers'])
403
296
        self.assertNotIn('body', first_message)
404
297
 
405
298
    def test_display_type_text_only(self):
475
368
        with ForkedFakeService.from_client(client, archive):
476
369
            with ExpectedException(UnparsableDateRange, ''):
477
370
                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'])