~didrocks/unity/altf10

« back to all changes in this revision

Viewing changes to grackle/tests/test_client.py

  • Committer: Curtis Hovey
  • Date: 2012-03-16 19:37:09 UTC
  • Revision ID: curtis.hovey@canonical.com-20120316193709-oa33ido3h6hpo0bu
Factor-out message methods.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
    HTTPServer,
3
3
    BaseHTTPRequestHandler,
4
4
    )
 
5
from email.message import Message
 
6
from email.mime.multipart import MIMEMultipart
 
7
from email.mime.text import MIMEText
5
8
import httplib
 
9
import logging
6
10
import os
7
11
from signal import SIGKILL
 
12
import simplejson
8
13
from StringIO import StringIO
 
14
import sys
9
15
from unittest import TestCase
 
16
from urlparse import urlparse
10
17
 
11
18
from testtools import ExpectedException
12
19
 
13
20
from grackle.client import (
14
21
    GrackleClient,
15
 
    )
16
 
 
17
 
 
18
 
class Forked:
19
 
 
20
 
    def __init__(self, func_or_method, *args):
21
 
        self.func_or_method = func_or_method
 
22
    UnparsableDateRange,
 
23
    UnsupportedDisplayType,
 
24
    UnsupportedOrder,
 
25
    )
 
26
from grackle.store import (
 
27
    make_json_message,
 
28
    MemoryStore,
 
29
    )
 
30
 
 
31
 
 
32
def make_message(message_id, body='body', headers=None, hidden=False):
 
33
    if headers is None:
 
34
        headers = {}
 
35
    message_headers = {
 
36
        'Message-Id': message_id,
 
37
        'date': '2005-01-01',
 
38
        'subject': 'subject',
 
39
        'from': 'author',
 
40
        'replies': '',
 
41
        }
 
42
    message_headers.update(headers.items())
 
43
    message = Message()
 
44
    message.set_payload(body)
 
45
    for key, value in message_headers.items():
 
46
        message[key] = value
 
47
    return make_json_message(message_id, message.as_string(), hidden)
 
48
 
 
49
 
 
50
def make_mime_message(message_id, body='body', headers=None, hidden=False,
 
51
                      attachment_type=None):
 
52
    parts = MIMEMultipart()
 
53
    parts.attach(MIMEText(body))
 
54
    if attachment_type is not None:
 
55
        attachment = Message()
 
56
        attachment.set_payload('attactment data.')
 
57
        attachment['Content-Type'] = attachment_type
 
58
        attachment['Content-Disposition'] = 'attachment; filename="file.ext"'
 
59
        parts.attach(attachment)
 
60
    return make_message(message_id, parts.as_string(), 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
        """
22
75
        self.pid = None
23
 
        self.args = args
 
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')
24
98
 
25
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."""
26
104
        pid = os.fork()
27
 
        if pid != 0:
28
 
            self.pid = pid
29
 
            return
30
 
        self.func_or_method(*self.args)
 
105
        if pid == 0:
 
106
            self.start_server()
 
107
        self.pid = pid
 
108
        os.read(self.read_end, 1)
 
109
        return
31
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()
32
123
 
33
124
    def __exit__(self, exc_type, exc_val, traceback):
34
125
        os.kill(self.pid, SIGKILL)
35
126
 
36
127
 
37
128
class FakeGrackleRequestHandler(BaseHTTPRequestHandler):
38
 
 
39
 
    def do_PUT(self):
40
 
        message = self.rfile.read(int(self.headers['content-length']))
41
 
        if message == 'This is a message':
42
 
            self.send_response(httplib.CREATED)
43
 
            self.end_headers()
44
 
            self.wfile.close()
 
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
        scheme, netloc, path, params, query_string, fragments = (
 
139
            urlparse(self.path))
 
140
        parts = path.split('/')
 
141
        if parts[1] != 'archive':
 
142
            # This is an unknonwn operation?
 
143
            return
 
144
        if 'content-length' in self.headers:
 
145
            operation = 'put_message'
45
146
        else:
46
 
            self.send_error(httplib.BAD_REQUEST)
47
 
 
48
 
 
49
 
def run_service(port):
50
 
    service = HTTPServer(('', port), FakeGrackleRequestHandler)
51
 
    service.serve_forever()
52
 
 
 
147
            operation = 'hide_message'
 
148
        if operation == 'put_message':
 
149
            message = self.rfile.read(int(self.headers['content-length']))
 
150
            try:
 
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)
 
154
                self.end_headers()
 
155
                self.wfile.close()
 
156
            except:
 
157
                self.send_error(httplib.BAD_REQUEST)
 
158
        elif operation == 'hide_message':
 
159
            try:
 
160
                # This expected path is /archive/archive_id/message_id.
 
161
                response = self.server.store.hide_message(
 
162
                    parts[2], parts[3], query_string)
 
163
                self.send_response(httplib.OK)
 
164
                self.end_headers()
 
165
                self.wfile.write(simplejson.dumps(response))
 
166
            except:
 
167
                self.send_error(httplib.BAD_REQUEST)
 
168
 
 
169
    def do_GET(self):
 
170
        """Retrieve a list of messages on GET."""
 
171
        scheme, netloc, path, params, query_string, fragments = (
 
172
            urlparse(self.path))
 
173
        parts = path.split('/')
 
174
        if parts[1] == 'archive':
 
175
            try:
 
176
                response = self.server.store.get_messages(
 
177
                    parts[2], query_string)
 
178
                self.send_response(httplib.OK)
 
179
                self.end_headers()
 
180
                self.wfile.write(simplejson.dumps(response))
 
181
            except Exception, error:
 
182
                self.send_response(
 
183
                    httplib.BAD_REQUEST, error.__doc__)
 
184
                return
 
185
 
 
186
    def log_message(self, format, *args):
 
187
        """Override log_message to use standard Python logging."""
 
188
        message = "%s - - [%s] %s\n" % (
 
189
            self.address_string(), self.log_date_time_string(), format % args)
 
190
        self.logger.info(message)
53
191
 
54
192
 
55
193
class TestPutMessage(TestCase):
56
194
 
57
195
    def test_put_message(self):
58
 
        client = GrackleClient('localhost', 8435)
59
 
        with Forked(run_service, client.port):
60
 
            client.put_message('arch1', 'asdf', StringIO('This is a message'))
 
196
        client = GrackleClient('localhost', 8420)
 
197
        message_archives = {'arch1': []}
 
198
        with ForkedFakeService.from_client(client, message_archives):
 
199
            client.put_message('arch1', 'id1', StringIO('This is a message'))
 
200
            response = client.get_messages('arch1')
 
201
        self.assertEqual(1, len(response['messages']))
 
202
        message = response['messages'][0]
 
203
        self.assertEqual('id1', message['message_id'])
 
204
 
 
205
    def test_put_message_without_archive(self):
 
206
        client = GrackleClient('localhost', 8421)
 
207
        message_archives = {'arch1': []}
 
208
        with ForkedFakeService.from_client(client, message_archives):
61
209
            with ExpectedException(Exception, 'wtf'):
62
 
                client.put_message('arch1', 'asdf',
63
 
                    StringIO('This is not a message'))
 
210
                client.put_message('no-archive', 'id1', StringIO('message'))
 
211
 
 
212
 
 
213
class TestGetMessages(TestCase):
 
214
 
 
215
    def assertIDOrder(self, ids, messages):
 
216
        self.assertEqual(ids, [m['message_id'] for m in messages])
 
217
 
 
218
    def assertMessageIDs(self, ids, messages):
 
219
        self.assertIDOrder(
 
220
            sorted(ids), sorted(messages, key=lambda m: m['message_id']))
 
221
 
 
222
    def test_get_messages(self):
 
223
        client = GrackleClient('localhost', 8430)
 
224
        archive = {
 
225
            'baz': [make_message('foo'), make_message('bar')]}
 
226
        with ForkedFakeService.from_client(client, archive):
 
227
            response = client.get_messages('baz')
 
228
        self.assertEqual(['bar', 'foo'], sorted(m['message_id'] for m in
 
229
            response['messages']))
 
230
        self.assertIs(None, response['next_memo'])
 
231
        self.assertIs(None, response['previous_memo'])
 
232
 
 
233
    def test_get_messages_by_id(self):
 
234
        client = GrackleClient('localhost', 8437)
 
235
        archive = {
 
236
            'baz': [make_message('foo'), make_message('bar')]}
 
237
        with ForkedFakeService.from_client(client, archive):
 
238
            response = client.get_messages('baz', message_ids=['foo'])
 
239
        message, = response['messages']
 
240
        self.assertEqual('foo', message['message_id'])
 
241
 
 
242
    def test_get_messages_batching(self):
 
243
        client = GrackleClient('localhost', 8438)
 
244
        archive = {'baz': [make_message('foo'), make_message('bar')]}
 
245
        with ForkedFakeService.from_client(client, archive):
 
246
            response = client.get_messages('baz', limit=1)
 
247
            self.assertEqual(1, len(response['messages']))
 
248
            messages = response['messages']
 
249
            response = client.get_messages(
 
250
                'baz', limit=1, memo=response['next_memo'])
 
251
            self.assertEqual(1, len(response['messages']))
 
252
            messages.extend(response['messages'])
 
253
            self.assertMessageIDs(['foo', 'bar'], messages)
 
254
 
 
255
    def get_messages_member_order_test(self, key):
 
256
        client = GrackleClient('localhost', 8439)
 
257
        if key == 'author':
 
258
            header_name = 'from'
 
259
        else:
 
260
            header_name = key
 
261
        archive = {
 
262
            'baz': [
 
263
                make_message('foo', headers={header_name: '2011-03-25'}),
 
264
                make_message('bar', headers={header_name: '2011-03-24'}),
 
265
             ]}
 
266
        with ForkedFakeService.from_client(client, archive):
 
267
            response = client.get_messages('baz')
 
268
            self.assertIDOrder(['foo', 'bar'], response['messages'])
 
269
            response = client.get_messages('baz', order=key)
 
270
            self.assertIDOrder(['bar', 'foo'], response['messages'])
 
271
 
 
272
    def test_get_messages_date_order(self):
 
273
        self.get_messages_member_order_test('date')
 
274
 
 
275
    def test_get_messages_author_order(self):
 
276
        self.get_messages_member_order_test('author')
 
277
 
 
278
    def test_get_messages_subject_order(self):
 
279
        self.get_messages_member_order_test('subject')
 
280
 
 
281
    def test_get_messages_thread_subject_order(self):
 
282
        archive = {
 
283
            'baz': [
 
284
                make_message('bar', headers={'subject': 'y'}),
 
285
                make_message('qux', headers={'subject': 'z'}),
 
286
                make_message('foo', headers={'subject': 'x',
 
287
                                             'in-reply-to': 'qux'}),
 
288
             ]}
 
289
        client = GrackleClient('localhost', 8439)
 
290
        with ForkedFakeService.from_client(client, archive):
 
291
            response = client.get_messages('baz')
 
292
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
293
            response = client.get_messages('baz', order='subject')
 
294
            self.assertIDOrder(['foo', 'bar', 'qux'], response['messages'])
 
295
            response = client.get_messages('baz', order='thread_subject')
 
296
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
297
 
 
298
    def test_get_messages_thread_oldest_order(self):
 
299
        client = GrackleClient('localhost', 8439)
 
300
        archive = {
 
301
            'baz': [
 
302
                make_message('bar', headers={'date': 'x'}),
 
303
                make_message('qux', headers={'date': 'z'}),
 
304
                make_message('foo', headers={'date': 'y',
 
305
                                             'in-reply-to': 'qux'}),
 
306
            ]}
 
307
        with ForkedFakeService.from_client(client, archive):
 
308
            response = client.get_messages('baz')
 
309
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
310
            response = client.get_messages('baz', order='date')
 
311
            self.assertIDOrder(['bar', 'foo', 'qux'], response['messages'])
 
312
            response = client.get_messages('baz', order='thread_oldest')
 
313
            self.assertIDOrder(['bar', 'qux', 'foo'], response['messages'])
 
314
 
 
315
    def test_get_messages_thread_newest_order(self):
 
316
        client = GrackleClient('localhost', 8439)
 
317
        archive = {
 
318
            'baz': [
 
319
                make_message('bar', headers={'date': 'x'}),
 
320
                make_message('qux', headers={'date': 'w'}),
 
321
                make_message('foo', headers={'date': 'y',
 
322
                                             'in-reply-to': 'bar'}),
 
323
                make_message('baz', headers={'date': 'z',
 
324
                                             'in-reply-to': 'qux'}),
 
325
            ]}
 
326
        with ForkedFakeService.from_client(client, archive):
 
327
            response = client.get_messages('baz', order='date')
 
328
            self.assertIDOrder(
 
329
                ['qux', 'bar', 'foo', 'baz'], response['messages'])
 
330
            response = client.get_messages('baz', order='thread_newest')
 
331
            self.assertIDOrder(
 
332
                ['bar', 'foo', 'qux', 'baz'], response['messages'])
 
333
 
 
334
    def test_get_messages_unsupported_order(self):
 
335
        client = GrackleClient('localhost', 8439)
 
336
        archive = {
 
337
            'baz': [
 
338
                make_message('foo', headers={'date': '2011-03-25'}),
 
339
                make_message('foo', headers={'date': '2011-03-24'}),
 
340
            ]}
 
341
        with ForkedFakeService.from_client(client, archive):
 
342
            with ExpectedException(UnsupportedOrder, ''):
 
343
                client.get_messages('baz', order='nonsense')
 
344
 
 
345
    def test_get_messages_headers_no_headers(self):
 
346
        client = GrackleClient('localhost', 8440)
 
347
        archive = {'baz': [make_message('foo')]}
 
348
        with ForkedFakeService.from_client(client, archive):
 
349
            response = client.get_messages('baz', headers=[
 
350
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
351
        first_message = response['messages'][0]
 
352
        self.assertEqual('foo', first_message['message_id'])
 
353
        self.assertEqual({}, first_message['headers'])
 
354
 
 
355
    def test_get_messages_headers_exclude_headers(self):
 
356
        client = GrackleClient('localhost', 8441)
 
357
        archive = {
 
358
            'baz': [make_message('foo', headers={'From': 'me'})]}
 
359
        with ForkedFakeService.from_client(client, archive):
 
360
            response = client.get_messages('baz', headers=[
 
361
                'Subject', 'Date', 'X-Launchpad-Message-Rationale'])
 
362
        first_message = response['messages'][0]
 
363
        self.assertEqual('foo', first_message['message_id'])
 
364
        self.assertEqual({}, first_message['headers'])
 
365
 
 
366
    def test_get_messages_headers_include_headers(self):
 
367
        client = GrackleClient('localhost', 8442)
 
368
        archive = {
 
369
            'baz': [
 
370
                make_message('foo', headers={'From': 'me', 'To': 'you'})]}
 
371
        with ForkedFakeService.from_client(client, archive):
 
372
            response = client.get_messages('baz', headers=[
 
373
                'From', 'To'])
 
374
        first_message = response['messages'][0]
 
375
        self.assertEqual('foo', first_message['message_id'])
 
376
        self.assertEqual({'From': 'me', 'To': 'you'}, first_message['headers'])
 
377
 
 
378
    def test_get_messages_max_body_length(self):
 
379
        client = GrackleClient('localhost', 8443)
 
380
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
381
        with ForkedFakeService.from_client(client, archive):
 
382
            response = client.get_messages('baz', max_body_length=3)
 
383
        first_message = response['messages'][0]
 
384
        self.assertEqual('abc', first_message['body'])
 
385
 
 
386
    def test_include_hidden(self):
 
387
        client = GrackleClient('localhost', 8444)
 
388
        archive = {
 
389
            'baz': [
 
390
                make_message('foo', hidden=True),
 
391
                make_message('bar', hidden=False),
 
392
            ]}
 
393
        with ForkedFakeService.from_client(client, archive):
 
394
            response = client.get_messages('baz', include_hidden=True)
 
395
            self.assertMessageIDs(['bar', 'foo'], response['messages'])
 
396
            response = client.get_messages('baz', include_hidden=False)
 
397
            self.assertMessageIDs(['bar'], response['messages'])
 
398
 
 
399
    def test_display_type_unknown_value(self):
 
400
        client = GrackleClient('localhost', 8445)
 
401
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
402
        with ForkedFakeService.from_client(client, archive):
 
403
            with ExpectedException(UnsupportedDisplayType, ''):
 
404
                client.get_messages('baz', display_type='unknown')
 
405
 
 
406
    def test_display_type_headers_only(self):
 
407
        client = GrackleClient('localhost', 8446)
 
408
        archive = {
 
409
            'baz': [
 
410
                make_message('foo', body=u'abcdefghi',
 
411
                             headers={'From': 'me', 'To': 'you'})]}
 
412
        with ForkedFakeService.from_client(client, archive):
 
413
            response = client.get_messages('baz', display_type='headers-only')
 
414
        first_message = response['messages'][0]
 
415
        self.assertEqual('foo', first_message['message_id'])
 
416
        self.assertEqual(
 
417
            archive['baz'][0]['headers'], first_message['headers'])
 
418
        self.assertNotIn('body', first_message)
 
419
 
 
420
    def test_display_type_text_only(self):
 
421
        client = GrackleClient('localhost', 8446)
 
422
        archive = {
 
423
            'baz': [
 
424
                make_mime_message(
 
425
                    'foo', 'abcdefghi',
 
426
                    headers={'From': 'me', 'To': 'you'},
 
427
                    attachment_type='text/x-diff')]}
 
428
        with ForkedFakeService.from_client(client, archive):
 
429
            response = client.get_messages('baz', display_type='text-only')
 
430
        first_message = response['messages'][0]
 
431
        self.assertEqual('foo', first_message['message_id'])
 
432
        self.assertEqual('me', first_message['headers']['From'])
 
433
        self.assertEqual('you', first_message['headers']['To'])
 
434
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
435
 
 
436
    def test_display_type_all(self):
 
437
        client = GrackleClient('localhost', 8447)
 
438
        archive = {
 
439
            'baz': [
 
440
                make_mime_message(
 
441
                    'foo', 'abcdefghi',
 
442
                    headers={'From': 'me', 'To': 'you'},
 
443
                    attachment_type='text/x-diff')]}
 
444
        with ForkedFakeService.from_client(client, archive):
 
445
            response = client.get_messages('baz', display_type='all')
 
446
        first_message = response['messages'][0]
 
447
        self.assertEqual('foo', first_message['message_id'])
 
448
        self.assertEqual('me', first_message['headers']['From'])
 
449
        self.assertEqual('you', first_message['headers']['To'])
 
450
        self.assertEqual(archive['baz'][0]['body'], first_message['body'])
 
451
 
 
452
    def test_date_range(self):
 
453
        client = GrackleClient('localhost', 8448)
 
454
        archive = {
 
455
            'baz': [
 
456
                make_mime_message(
 
457
                    'foo', 'abcdefghi', headers={'date': '2011-12-31'}),
 
458
                make_mime_message(
 
459
                    'bar', 'abcdefghi', headers={'date': '2012-01-01'}),
 
460
                make_mime_message(
 
461
                    'qux', 'abcdefghi', headers={'date': '2012-01-15'}),
 
462
                make_mime_message(
 
463
                    'naf', 'abcdefghi', headers={'date': '2012-01-31'}),
 
464
                make_mime_message(
 
465
                    'doh', 'abcdefghi', headers={'date': '2012-02-01'}),
 
466
                    ]}
 
467
        with ForkedFakeService.from_client(client, archive):
 
468
            response = client.get_messages(
 
469
                'baz', date_range='2012-01-01..2012-01-31')
 
470
        ids = sorted(m['message_id'] for m in response['messages'])
 
471
        self.assertEqual(['bar', 'naf', 'qux'], ids)
 
472
 
 
473
    def test_date_range_unparsabledaterange(self):
 
474
        client = GrackleClient('localhost', 8449)
 
475
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
476
        with ForkedFakeService.from_client(client, archive):
 
477
            with ExpectedException(UnparsableDateRange, ''):
 
478
                client.get_messages('baz', date_range='2012-01-01')
 
479
 
 
480
    def test_date_range_unparsabledaterange_missing_part(self):
 
481
        client = GrackleClient('localhost', 8450)
 
482
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
483
        with ForkedFakeService.from_client(client, archive):
 
484
            with ExpectedException(UnparsableDateRange, ''):
 
485
                client.get_messages('baz', date_range='2012-01-01..')
 
486
 
 
487
    def test_date_range_unparsabledaterange_extra_part(self):
 
488
        client = GrackleClient('localhost', 8451)
 
489
        archive = {'baz': [make_message('foo', body=u'abcdefghi')]}
 
490
        with ForkedFakeService.from_client(client, archive):
 
491
            with ExpectedException(UnparsableDateRange, ''):
 
492
                client.get_messages('baz', date_range='2012-01..12-02..12-03')
 
493
 
 
494
 
 
495
class TestHideMessages(TestCase):
 
496
 
 
497
    def test_hide_message_true(self):
 
498
        client = GrackleClient('localhost', 8470)
 
499
        archive = {
 
500
            'baz': [
 
501
                make_message('foo', hidden=False),
 
502
            ]}
 
503
        with ForkedFakeService.from_client(client, archive):
 
504
            response = client.hide_message('baz', 'foo', hidden=True)
 
505
        self.assertEqual('foo', response['message_id'])
 
506
        self.assertIs(True, response['hidden'])
 
507
 
 
508
    def test_hide_message_false(self):
 
509
        client = GrackleClient('localhost', 8470)
 
510
        archive = {
 
511
            'baz': [
 
512
                make_message('foo', hidden=True),
 
513
            ]}
 
514
        with ForkedFakeService.from_client(client, archive):
 
515
            response = client.hide_message('baz', 'foo', hidden=False)
 
516
        self.assertEqual('foo', response['message_id'])
 
517
        self.assertIs(False, response['hidden'])