Completed
Push — master ( e98a68...1556f2 )
by Andreas
11s
created

TestBtcdeAPIDocu.test_decimal_parsing()   A

Complexity

Conditions 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
c 1
b 0
f 1
dl 0
loc 15
rs 9.4285
1
from unittest import TestCase
2
import hashlib
3
import hmac
4
import requests_mock
5
from mock import patch
6
import json
7
import btcde
8
from decimal import Decimal
9
10
11
@patch('btcde.log')
12
@requests_mock.Mocker()
13
class TestBtcdeAPIDocu(TestCase):
14
    '''Tests are as in bitcoin.de API documentation.
15
    https://www.bitcoin.de/de/api/tapi/v2/docu'''
16
17
    def sampleData(self, file):
18
        '''Retrieve sample data from json files.'''
19
        filepath = 'tests/resources/{}.json'.format(file)
20
        data = json.load(open(filepath))
21
        return data
22 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
23
    def sortParams(self, url, params={}):
24
        '''To sort params for url string.'''
25
        self.encoded_string = ''
26
        if len(params) > 0:
27
            for key, value in sorted(params.items()):
28
                self.encoded_string += str(key) + '=' + str(value) + '&'
29
            self.encoded_string = self.encoded_string[:-1]
30
            self.url = url + '?' + self.encoded_string
31
        else:
32
            self.url = url
33
        return self.url, self.encoded_string
34
35
    def verifySignature(self, url, method, params):
36
        '''To verify API Signature.'''
37
        self.XAPINONCE += 1
38
        self.url, self.encoded_string = self.sortParams(url, params)
39
        if method == 'POST':
40
            md5_encoded_query_string = hashlib.md5(self.encoded_string.encode()
41
                                                   ).hexdigest()
42
        else:
43
            md5_encoded_query_string = hashlib.md5(b'').hexdigest()
44
        hmac_data = '{method}#{url}#{key}#{nonce}#{md5}'\
45
                    .format(method=method,
46
                            url=self.url,
47
                            key=self.XAPIKEY,
48
                            nonce=str(self.XAPINONCE),
49
                            md5=md5_encoded_query_string)
50
        hmac_signed = hmac.new(bytearray(self.XAPISECRET.encode()),
51
                               msg=hmac_data.encode(),
52
                               digestmod=hashlib.sha256).hexdigest()
53
        return hmac_signed
54
55
    def setUp(self):
56
        self.XAPIKEY = 'f00b4r'
57
        self.XAPISECRET = 'b4rf00'
58
        self.conn = btcde.Connection(self.XAPIKEY, self.XAPISECRET)
59
        self.XAPINONCE = self.conn.nonce
60
61
    def tearDown(self):
62
        del self.XAPIKEY
63
        del self.XAPISECRET
64
        del self.conn
65 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
66
    def test_signature_post(self, mock_logger, m):
67
        '''Test signature on a post request.'''
68
        params = {'type': 'buy',
69
                  'trading_pair': 'btceur',
70
                  'max_amount': 10,
71
                  'price': 1337}
72
        response = self.sampleData('createOrder')
73
        m.post(requests_mock.ANY, json=response, status_code=201)
74
        self.conn.createOrder(params.get('type'),
75
                              params.get('trading_pair'),
76
                              params.get('max_amount'),
77
                              params.get('price'))
78
        history = m.request_history
79
        request_signature = history[0].headers.get('X-API-SIGNATURE')
80
        verified_signature = self.verifySignature(self.conn.orderuri,
81
                                                  'POST',
82
                                                  params)
83
        self.assertEqual(request_signature, verified_signature)
84
        self.assertTrue(mock_logger.debug.called)
85 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
86
    def test_signature_get(self, mock_logger, m):
87
        '''Test signature on a get request.'''
88
        params = {'type': 'buy',
89
                  'trading_pair': 'btceur'}
90
        response = self.sampleData('showOrderbook_buy')
91
        m.get(requests_mock.ANY, json=response, status_code=200)
92
        self.conn.showOrderbook(params.get('type'),
93
                                params.get('trading_pair'))
94
        history = m.request_history
95
        request_signature = history[0].headers.get('X-API-SIGNATURE')
96
        verified_signature = self.verifySignature(self.conn.orderuri,
97
                                                  'GET',
98
                                                  params)
99
        self.assertEqual(request_signature, verified_signature)
100
        self.assertTrue(mock_logger.debug.called)
101
102
    def test_signature_delete(self, mock_logger, m):
103
        '''Test signature on a delete request.'''
104
        order_id = 'A1234BC'
105
        trading_pair = 'btceur'
106
        m.delete(requests_mock.ANY, json={}, status_code=200)
107
        self.conn.deleteOrder(order_id, trading_pair)
108
        history = m.request_history
109
        request_signature = history[0].headers.get('X-API-SIGNATURE')
110
        url = self.conn.orderuri + "/" + order_id + "/" + trading_pair
111
        verified_signature = self.verifySignature(url, 'DELETE', {})
112
        self.assertEqual(request_signature, verified_signature)
113
        self.assertTrue(mock_logger.debug.called)
114 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
115
    def test_show_orderbook(self, mock_logger, m):
116
        '''Test function showOrderbook.'''
117
        params = {'type': 'buy',
118
                  'trading_pair': 'btceur',
119
                  'max_amount': 10,
120
                  'price': 1337}
121
        base_url = 'https://api.bitcoin.de/v2/orders'
122
        url_args = '?price={}&trading_pair={}&type={}'\
123
                   .format(params.get('price'),
124
                           params.get('trading_pair'),
125
                           params.get('type'))
126
        response = self.sampleData('showOrderbook_buy')
127
        m.get(requests_mock.ANY, json=response, status_code=200)
128
        self.conn.showOrderbook(params.get('type'),
129
                                params.get('trading_pair'),
130
                                price=params.get('price'))
131
        history = m.request_history
132
        self.assertEqual(history[0].method, "GET")
133
        self.assertEqual(history[0].url, base_url + url_args)
134
        self.assertTrue(mock_logger.debug.called)
135 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
136
    def test_createOrder(self, mock_logger, m):
137
        '''Test function createOrder.'''
138
        params = {'type': 'buy',
139
                  'trading_pair': 'btceur',
140
                  'max_amount': '10',
141
                  'price': '10'}
142
        base_url = 'https://api.bitcoin.de/v2/orders'
143
        url_args = '?max_amount={}&price={}&trading_pair={}&type={}'\
144
                   .format(params.get('max_amount'),
145
                           params.get('price'),
146
                           params.get('trading_pair'),
147
                           params.get('type'))
148
        response = self.sampleData('createOrder')
149
        m.post(requests_mock.ANY, json=response, status_code=201)
150
        self.conn.createOrder(params.get('type'),
151
                              params.get('trading_pair'),
152
                              params.get('max_amount'),
153
                              params.get('price'))
154
        history = m.request_history
155
        self.assertEqual(history[0].method, "POST")
156
        self.assertEqual(history[0].url, base_url + url_args)
157
        self.assertTrue(mock_logger.debug.called)
158 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
159
    def test_deleteOrder(self, mock_logger, m):
160
        '''Test function deleteOrder.'''
161
        params = {'trading_pair': 'btceur',
162
                  'order_id': '1337'}
163
        base_url = 'https://api.bitcoin.de/v2/orders'
164
        url_args = '/{}/{}'.format(params.get('order_id'),
165
                                   params.get('trading_pair'))
166
        response = self.sampleData('deleteOrder')
167
        m.delete(requests_mock.ANY, json=response, status_code=200)
168
        self.conn.deleteOrder(params.get('order_id'),
169
                              params.get('trading_pair'))
170
        history = m.request_history
171
        self.assertEqual(history[0].method, "DELETE")
172
        self.assertEqual(history[0].url, base_url + url_args)
173
        self.assertTrue(mock_logger.debug.called)
174 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
175
    def test_showMyOrders(self, mock_logger, m):
176
        '''Test function showMyOrders.'''
177
        params = {'type': 'buy',
178
                  'trading_pair': 'btceur'}
179
        base_url = 'https://api.bitcoin.de/v2/orders/my_own'
180
        url_args = '?trading_pair={}&type={}'\
181
                   .format(params.get('trading_pair'),
182
                           params.get('type'))
183
        response = self.sampleData('showMyOrders')
184
        m.get(requests_mock.ANY, json=response, status_code=200)
185
        self.conn.showMyOrders(type=params.get('type'),
186
                               trading_pair=params.get('trading_pair'))
187
        history = m.request_history
188
        self.assertEqual(history[0].method, "GET")
189
        self.assertEqual(history[0].url, base_url + url_args)
190
        self.assertTrue(mock_logger.debug.called)
191 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
192
    def test_showMyOrderDetails(self, mock_logger, m):
193
        '''Test function showMyOrderDetails.'''
194
        params = {'order_id': '1337'}
195
        base_url = 'https://api.bitcoin.de/v2/orders/{}'\
196
                   .format(params.get('order_id'))
197
        url_args = ''
198
        response = self.sampleData('showMyOrderDetails')
199
        m.get(requests_mock.ANY, json=response, status_code=200)
200
        self.conn.showMyOrderDetails(params.get('order_id'))
201
        history = m.request_history
202
        self.assertEqual(history[0].method, "GET")
203
        self.assertEqual(history[0].url, base_url + url_args)
204
        self.assertTrue(mock_logger.debug.called)
205 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
206
    def test_executeTrade(self, mock_logger, m):
207
        '''Test function executeTrade.'''
208
        params = {'type': 'buy',
209
                  'trading_pair': 'btceur',
210
                  'amount': '10',
211
                  'order_id': '1337'}
212
        base_url = 'https://api.bitcoin.de/v2/trades/{}'\
213
                   .format(params.get('order_id'))
214
        url_args = '?amount={}&order_id={}&trading_pair={}&type={}'\
215
                   .format(params.get('amount'),
216
                           params.get('order_id'),
217
                           params.get('trading_pair'),
218
                           params.get('type'))
219
        response = self.sampleData('executeTrade')
220
        m.get(requests_mock.ANY, json=response, status_code=200)
221
        m.post(requests_mock.ANY, json=response, status_code=201)
222
        self.conn.executeTrade(params.get('order_id'),
223
                               params.get('type'),
224
                               params.get('trading_pair'),
225
                               params.get('amount'))
226
        history = m.request_history
227
        self.assertEqual(history[0].method, "POST")
228
        self.assertEqual(history[0].url, base_url + url_args)
229
        self.assertTrue(mock_logger.debug.called)
230 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
231
    def test_showMyTrades(self, mock_logger, m):
232
        '''Test function showMyTrades.'''
233
        base_url = 'https://api.bitcoin.de/v2/trades'
234
        url_args = ''
235
        response = self.sampleData('showMyTrades')
236
        m.get(requests_mock.ANY, json=response, status_code=200)
237
        self.conn.showMyTrades()
238
        history = m.request_history
239
        self.assertEqual(history[0].method, "GET")
240
        self.assertEqual(history[0].url, base_url + url_args)
241
        self.assertTrue(mock_logger.debug.called)
242 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
243
    def test_showMyTrades_with_params(self, mock_logger, m):
244
        '''Test function showMyTrades with parameters.'''
245
        params = {'type': 'buy',
246
                  'trading_pair': 'btceur'}
247
        base_url = 'https://api.bitcoin.de/v2/trades'
248
        url_args = '?trading_pair={}&type={}'\
249
                   .format(params.get('trading_pair'),
250
                           params.get('type'))
251
        response = self.sampleData('showMyTrades')
252
        m.get(requests_mock.ANY, json=response, status_code=200)
253
        self.conn.showMyTrades(type=params.get('type'),
254
                               trading_pair=params.get('trading_pair'))
255
        history = m.request_history
256
        self.assertEqual(history[0].method, "GET")
257
        self.assertEqual(history[0].url, base_url + url_args)
258
        self.assertTrue(mock_logger.debug.called)
259 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
260
    def test_showMyTradeDetails(self, mock_logger, m):
261
        '''Test function showMyTradeDetails.'''
262
        params = {'trade_id': '1337'}
263
        base_url = 'https://api.bitcoin.de/v2/trades'
264
        url_args = '/{}'.format(params.get('trade_id'))
265
        response = self.sampleData('showMyTradeDetails')
266
        m.get(requests_mock.ANY, json=response, status_code=200)
267
        self.conn.showMyTradeDetails(params.get('trade_id'))
268
        history = m.request_history
269
        self.assertEqual(history[0].method, "GET")
270
        self.assertEqual(history[0].url, base_url + url_args)
271
        self.assertTrue(mock_logger.debug.called)
272 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
273
    def test_showAccountInfo(self, mock_logger, m):
274
        '''Test function showAccountInfo.'''
275
        base_url = 'https://api.bitcoin.de/v2/account'
276
        url_args = ''
277
        response = self.sampleData('showAccountInfo')
278
        m.get(requests_mock.ANY, json=response, status_code=200)
279
        self.conn.showAccountInfo()
280
        history = m.request_history
281
        self.assertEqual(history[0].method, "GET")
282
        self.assertEqual(history[0].url, base_url + url_args)
283
        self.assertTrue(mock_logger.debug.called)
284 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
285
    def test_showOrderbookCompact(self, mock_logger, m):
286
        '''Test function showOrderbookCompact.'''
287
        params = {'trading_pair': 'btceur'}
288
        base_url = 'https://api.bitcoin.de/v2/orders/compact'
289
        url_args = '?trading_pair={}'.format(params.get('trading_pair'))
290
        response = self.sampleData('showOrderbookCompact')
291
        m.get(requests_mock.ANY, json=response, status_code=200)
292
        self.conn.showOrderbookCompact(params.get('trading_pair'))
293
        history = m.request_history
294
        self.assertEqual(history[0].method, "GET")
295
        self.assertEqual(history[0].url, base_url + url_args)
296
        self.assertTrue(mock_logger.debug.called)
297 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
298
    def test_showPublicTradeHistory(self, mock_logger, m):
299
        '''Test function showPublicTradeHistory.'''
300
        params = {'trading_pair': 'btceur'}
301
        base_url = 'https://api.bitcoin.de/v2/trades/history'
302
        url_args = '?trading_pair={}'.format(params.get('trading_pair'))
303
        response = self.sampleData('showPublicTradeHistory')
304
        m.get(requests_mock.ANY, json=response, status_code=200)
305
        self.conn.showPublicTradeHistory(params.get('trading_pair'))
306
        history = m.request_history
307
        self.assertEqual(history[0].method, "GET")
308
        self.assertEqual(history[0].url, base_url + url_args)
309
        self.assertTrue(mock_logger.debug.called)
310 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
311
    def test_showPublicTradeHistory_since(self, mock_logger, m):
312
        '''Test function showPublicTradeHistory with since_tid.'''
313
        params = {'trading_pair': 'btceur', 'since_tid': '123'}
314
        base_url = 'https://api.bitcoin.de/v2/trades/history'
315
        url_args = '?since_tid={}&trading_pair={}'.format(params.get('since_tid'),
316
                                                          params.get('trading_pair'))
317
        response = self.sampleData('showPublicTradeHistory')
318
        m.get(requests_mock.ANY, json=response, status_code=200)
319
        self.conn.showPublicTradeHistory(params.get('trading_pair'),
320
                                         since_tid=params.get('since_tid'))
321
        history = m.request_history
322
        self.assertEqual(history[0].method, "GET")
323
        self.assertEqual(history[0].url, base_url + url_args)
324
        self.assertTrue(mock_logger.debug.called)
325 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
326
    def test_showRates(self, mock_logger, m):
327
        '''Test function showRates.'''
328
        params = {'trading_pair': 'btceur'}
329
        base_url = 'https://api.bitcoin.de/v2/rates'
330
        url_args = '?trading_pair={}'.format(params.get('trading_pair'))
331
        response = self.sampleData('showRates')
332
        m.get(requests_mock.ANY, json=response, status_code=200)
333
        self.conn.showRates(params.get('trading_pair'))
334
        history = m.request_history
335
        self.assertEqual(history[0].method, "GET")
336
        self.assertEqual(history[0].url, base_url + url_args)
337
        self.assertTrue(mock_logger.debug.called)
338 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
339
    def test_showAccountLedger(self, mock_logger, m):
340
        '''Test function showAccountLedger.'''
341
        params = {'currency': 'btc'}
342
        base_url = 'https://api.bitcoin.de/v2/account/ledger'
343
        url_args = '?currency={}'.format(params.get('currency'))
344
        response = self.sampleData('showAccountLedger')
345
        m.get(requests_mock.ANY, json=response, status_code=200)
346
        self.conn.showAccountLedger(params.get('currency'))
347
        history = m.request_history
348
        self.assertEqual(history[0].method, "GET")
349
        self.assertEqual(history[0].url, base_url + url_args)
350
        self.assertTrue(mock_logger.debug.called)
351
352
    def test_decimal_parsing(self, mock_logger, m):
353
        '''Test if the decimal parsing calculates correctly.'''
354
        params = {'type': 'buy',
355
                  'trading_pair': 'btceur',
356
                  'max_amount': 10,
357
                  'price': 1337}
358
        response = self.sampleData('showOrderbook_buy')
359
        m.get(requests_mock.ANY, json=response, status_code=200)
360
        data = self.conn.showOrderbook(params.get('type'),
361
                                       params.get('trading_pair'),
362
                                       price=params.get('price'))
363
        price = data.get('orders')[0].get('price')
364
        self.assertIsInstance(price, Decimal)
365
        self.assertEqual(price + Decimal('22.3'), Decimal('2232.2'))
366
        self.assertNotEqual(float(price) + float('22.3'), float('2232.2'))
367
368
369
class TestBtcdeExceptions(TestCase):
370
    '''Test for Exception Handling.'''
371
372 View Code Duplication
    def sampleData(self, file):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
373
        '''Retrieve sample data from json files.'''
374
        filepath = 'tests/resources/{}.json'.format(file)
375
        data = json.load(open(filepath))
376
        return data
377
378
    def setUp(self):
379
        self.XAPIKEY = 'f00b4r'
380
        self.XAPISECRET = 'b4rf00'
381
        self.conn = btcde.Connection(self.XAPIKEY, self.XAPISECRET)
382
        self.XAPINONCE = self.conn.nonce
383
384
    def tearDown(self):
385
        del self.XAPIKEY
386
        del self.XAPISECRET
387
        del self.conn
388
389
    @requests_mock.Mocker()
390
    def test_dont_fail_on_non_utf8(self, m):
391
        '''Test if no exception raises with a non-utf8 response.
392
        https://github.com/peshay/btcde/issues/12'''
393
        filepath = 'tests/resources/NonUTF8'
394
        with open(filepath, 'r') as f:
395
            m.post(requests_mock.ANY, content=f.read().encode('utf-16', 'replace'), status_code=403)
396
        try:
397
            self.conn.executeTrade('foobar', 'buy', 'btceur', '0')
398
            self.assertTrue(True)
399
        except UnicodeDecodeError:
400
            self.assertTrue(False)
401
402
    @requests_mock.Mocker()
403
    def test_APIException(self, m):
404
        '''Test API Exception.'''
405
        params = {'type': 'buy',
406
                  'trading_pair': 'btceur',
407
                  'max_amount': 10,
408
                  'price': 13}
409
        base_url = 'https://api.bitcoin.de/v2/orders'
410
        url_args = '?max_amount={}&price={}&trading_pair={}&type={}'\
411
                   .format(params.get('max_amount'),
412
                           params.get('price'),
413
                           params.get('trading_pair'),
414
                           params.get('type'))
415
        response = self.sampleData('error')
416
        m.post(requests_mock.ANY, json=response, status_code=400)
417
        self.conn.createOrder(params.get('type'),
418
                              params.get('trading_pair'), params.get('max_amount'),
419
                              price=params.get('price'))
420
        history = m.request_history
421
        self.assertEqual(history[0].method, "POST")
422
        self.assertEqual(history[0].url, base_url + url_args)
423
424
    @patch('btcde.log')
425
    def test_RequestException(self, mock_logger):
426
        '''Test Requests Exception.'''
427
        params = {'type': 'buy',
428
                  'trading_pair': 'btceur',
429
                  'max_amount': 10,
430
                  'price': 13}
431
        self.conn.orderuri = 'https://foo.bar'
432
        self.conn.createOrder(params.get('type'),
433
                              params.get('trading_pair'), params.get('max_amount'),
434
                              price=params.get('price'))
435
        self.assertTrue(mock_logger.warning.called)
436
437
    def test_TradingPairValueException(self):
438
        '''Test wrong traiding_pair Value Exception.'''
439
        with self.assertRaises(ValueError) as context:
440
            self.conn.deleteOrder('123', 'usdeur')
441
        self.assertTrue('usdeur is not any of' in str(context.exception))
442
443
    def test_OrderTypeValueException(self):
444
        '''Test wrong type Value Exception.'''
445
        with self.assertRaises(ValueError) as context:
446
            self.conn.createOrder('fail', 'btceur', '100', '100')
447
        self.assertTrue('fail is not any of' in str(context.exception))
448
449
    def test_CurrencyValueException(self):
450
        '''Test wrong currency Value Exception.'''
451
        with self.assertRaises(ValueError) as context:
452
            self.conn.showAccountLedger('usd')
453
        self.assertTrue('usd is not any of' in str(context.exception))
454
455
    def test_BankSeatValueException(self):
456
        '''Test wrong seat_of_bank Value Exception.'''
457
        with self.assertRaises(ValueError) as context:
458
            self.conn.showOrderbook('buy', 'btceur', seat_of_bank='SZ')
459
        self.assertTrue('SZ is not any of' in str(context.exception))
460
461
    def test_TrustLevelValueException(self):
462
        '''Test wrong trust_level Value Exception.'''
463
        with self.assertRaises(ValueError) as context:
464
            self.conn.createOrder('buy', 'btceur', '100', '100',
465
                                   min_trust_level='foo')
466
        self.assertTrue('foo is not any of' in str(context.exception))
467
468
    def test_PaymentOptionValueException(self):
469
        '''Test wrong payment_option Value Exception.'''
470
        with self.assertRaises(ValueError) as context:
471
            self.conn.createOrder('buy', 'btceur', '100', '100',
472
                                   payment_option=4)
473
        self.assertTrue('4 is not any of' in str(context.exception))
474
475
    def test_StateValueException(self):
476
        '''Test wrong state Value Exception.'''
477
        with self.assertRaises(ValueError) as context:
478
            self.conn.showMyOrders(state=4)
479
        self.assertTrue('4 is not any of' in str(context.exception))
480
481
    def test_UnknownKeyException(self):
482
        '''Test wrong Key Exception.'''
483
        with self.assertRaises(KeyError) as context:
484
            self.conn.showMyOrders(foo=4)
485
        self.assertTrue('foo is not any of' in str(context.exception))
486