Statistics
| Branch: | Tag: | Revision:

root / astakosclient / astakosclient / tests.py @ 1ecb12b5

History | View | Annotate | Download (22.5 kB)

1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2012, 2013 GRNET S.A. All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or
6
# without modification, are permitted provided that the following
7
# conditions are met:
8
#
9
#   1. Redistributions of source code must retain the above
10
#      copyright notice, this list of conditions and the following
11
#      disclaimer.
12
#
13
#   2. Redistributions in binary form must reproduce the above
14
#      copyright notice, this list of conditions and the following
15
#      disclaimer in the documentation and/or other materials
16
#      provided with the distribution.
17
#
18
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
# POSSIBILITY OF SUCH DAMAGE.
30
#
31
# The views and conclusions contained in the software and
32
# documentation are those of the authors and should not be
33
# interpreted as representing official policies, either expressed
34
# or implied, of GRNET S.A.
35

    
36
"""Unit Tests for the astakos-client module
37

38
Provides unit tests for the code implementing
39
the astakos client library
40

41
"""
42

    
43
import sys
44
import socket
45
import simplejson
46

    
47
import astakosclient
48
from astakosclient import AstakosClient
49
from astakosclient.errors import \
50
    AstakosClientException, Unauthorized, BadRequest, NotFound, \
51
    NoUserName, NoUUID
52

    
53
# Use backported unittest functionality if Python < 2.7
54
try:
55
    import unittest2 as unittest
56
except ImportError:
57
    if sys.version_info < (2, 7):
58
        raise Exception("The unittest2 package is required for Python < 2.7")
59
    import unittest
60

    
61

    
62
# --------------------------------------------------------------------
63
# Helper functions
64

    
65
# ----------------------------
66
# This functions will be used as mocked requests
67
def _request_offline(conn, method, url, **kwargs):
68
    """This request behaves as we were offline"""
69
    raise socket.gaierror
70

    
71

    
72
def _request_status_302(conn, method, url, **kwargs):
73
    """This request returns 302"""
74
    status = 302
75
    data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
76
        '<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
77
        '<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
78
    return (data, status)
79

    
80

    
81
def _request_status_404(conn, method, url, **kwargs):
82
    """This request returns 404"""
83
    status = 404
84
    data = '<html><head><title>404 Not Found</title></head>' \
85
        '<body><h1>Not Found</h1><p>The requested URL /foo was ' \
86
        'not found on this server.</p><hr><address>Apache Server ' \
87
        'at example.com Port 80</address></body></html>'
88
    return (data, status)
89

    
90

    
91
def _request_status_401(conn, method, url, **kwargs):
92
    """This request returns 401"""
93
    status = 401
94
    data = "Invalid X-Auth-Token\n"
95
    return (data, status)
96

    
97

    
98
def _request_status_400(conn, method, url, **kwargs):
99
    """This request returns 400"""
100
    status = 400
101
    data = "Method not allowed.\n"
102
    return (data, status)
103

    
104

    
105
def _request_ok(conn, method, url, **kwargs):
106
    """This request behaves like original Astakos does"""
107
    if url[0:16] == "/im/authenticate":
108
        return _req_authenticate(conn, method, url, **kwargs)
109
    elif url[0:14] == "/user_catalogs":
110
        return _req_catalogs(conn, method, url, **kwargs)
111
    else:
112
        return _request_status_404(conn, method, url, **kwargs)
113

    
114

    
115
def _req_authenticate(conn, method, url, **kwargs):
116
    """Check if user exists and return his profile"""
117
    global user_1, user_2
118

    
119
    # Check input
120
    if conn.__class__.__name__ != "HTTPSConnection":
121
        return _request_status_302(conn, method, url, **kwargs)
122

    
123
    if method != "GET":
124
        return _request_status_400(conn, method, url, **kwargs)
125

    
126
    token = kwargs['headers']['X-Auth-Token']
127
    if token == token_1:
128
        user = dict(user_1)
129
    elif token == token_2:
130
        user = dict(user_2)
131
    else:
132
        # No user found
133
        return _request_status_401(conn, method, url, **kwargs)
134

    
135
    # Return
136
    if "usage=1" not in url:
137
        # Strip `usage' key from `user'
138
        del user['usage']
139
    return (simplejson.dumps(user), 200)
140

    
141

    
142
def _req_catalogs(conn, method, url, **kwargs):
143
    """Return user catalogs"""
144
    global token_1, token_2, user_1, user_2
145

    
146
    # Check input
147
    if conn.__class__.__name__ != "HTTPSConnection":
148
        return _request_status_302(conn, method, url, **kwargs)
149

    
150
    if method != "POST":
151
        return _request_status_400(conn, method, url, **kwargs)
152

    
153
    token = kwargs['headers']['X-Auth-Token']
154
    if token != token_1 and token != token_2:
155
        return _request_status_401(conn, method, url, **kwargs)
156

    
157
    # Return
158
    body = simplejson.loads(kwargs['body'])
159
    if 'uuids' in body:
160
        # Return uuid_catalog
161
        uuids = body['uuids']
162
        catalogs = {}
163
        if user_1['uuid'] in uuids:
164
            catalogs[user_1['uuid']] = user_1['username']
165
        if user_2['uuid'] in uuids:
166
            catalogs[user_2['uuid']] = user_2['username']
167
        return_catalog = {"displayname_catalog": {}, "uuid_catalog": catalogs}
168
    elif 'displaynames' in body:
169
        # Return displayname_catalog
170
        names = body['displaynames']
171
        catalogs = {}
172
        if user_1['username'] in names:
173
            catalogs[user_1['username']] = user_1['uuid']
174
        if user_2['username'] in names:
175
            catalogs[user_2['username']] = user_2['uuid']
176
        return_catalog = {"displayname_catalog": catalogs, "uuid_catalog": {}}
177
    else:
178
        return_catalog = {"displayname_catalog": {}, "uuid_catalog": {}}
179
    return (simplejson.dumps(return_catalog), 200)
180

    
181

    
182
# ----------------------------
183
# Mock the actual _doRequest
184
def _mock_request(new_requests):
185
    """Mock the actual request
186

187
    Given a list of requests to use (in rotation),
188
    replace the original _doRequest function with
189
    a new one
190

191
    """
192
    def _mock(conn, method, url, **kwargs):
193
        # Get first request
194
        request = _mock.requests[0]
195
        # Rotate requests
196
        _mock.requests = _mock.requests[1:] + _mock.requests[:1]
197
        # Use first request
198
        return request(conn, method, url, **kwargs)
199

    
200
    _mock.requests = new_requests
201
    # Replace `_doRequest' with our `_mock'
202
    astakosclient._do_request = _mock
203

    
204

    
205
# ----------------------------
206
# Local users
207
token_1 = "skzleaFlBl+fasFdaf24sx=="
208
user_1 = \
209
    {"username": "user1@example.com",
210
     "auth_token_created": 1359386939000,
211
     "name": "Example User One",
212
     "email": ["user1@example.com"],
213
     "auth_token_expires": 1361978939000,
214
     "id": 108,
215
     "uuid": "73917abc-abcd-477e-a1f1-1763abcdefab",
216
     "usage": [
217
         {"currValue": 42949672960,
218
          "display_name": "System Disk",
219
          "name": "cyclades.disk"},
220
         {"currValue": 4,
221
          "display_name": "CPU",
222
          "name": "cyclades.cpu"},
223
         {"currValue": 4294967296,
224
          "display_name": "RAM",
225
          "name": "cyclades.ram"},
226
         {"currValue": 3,
227
          "display_name": "VM",
228
          "name": "cyclades.vm"},
229
         {"currValue": 0,
230
          "display_name": "private network",
231
          "name": "cyclades.network.private"},
232
         {"currValue": 152,
233
          "display_name": "Storage Space",
234
          "name": "pithos+.diskspace"}]}
235

    
236
token_2 = "fasdfDSFdf98923DF+sdfk=="
237
user_2 = \
238
    {"username": "user2@example.com",
239
     "auth_token_created": 1358386938997,
240
     "name": "Example User Two",
241
     "email": ["user1@example.com"],
242
     "auth_token_expires": 1461998939000,
243
     "id": 109,
244
     "uuid": "73917bca-1234-5678-a1f1-1763abcdefab",
245
     "usage": [
246
         {"currValue": 68719476736,
247
          "display_name": "System Disk",
248
          "name": "cyclades.disk"},
249
         {"currValue": 1,
250
          "display_name": "CPU",
251
          "name": "cyclades.cpu"},
252
         {"currValue": 1073741824,
253
          "display_name": "RAM",
254
          "name": "cyclades.ram"},
255
         {"currValue": 2,
256
          "display_name": "VM",
257
          "name": "cyclades.vm"},
258
         {"currValue": 1,
259
          "display_name": "private network",
260
          "name": "cyclades.network.private"},
261
         {"currValue": 2341634510,
262
          "display_name": "Storage Space",
263
          "name": "pithos+.diskspace"}]}
264

    
265

    
266
# --------------------------------------------------------------------
267
# The actual tests
268

    
269
class TestCallAstakos(unittest.TestCase):
270
    """Test cases for function _callAstakos"""
271

    
272
    # ----------------------------------
273
    # Test the response we get if we don't have internet access
274
    def _offline(self, pool):
275
        global token_1
276
        _mock_request([_request_offline])
277
        try:
278
            client = AstakosClient("https://example.com", use_pool=pool)
279
            client._call_astakos(token_1, "/im/authenticate")
280
        except AstakosClientException:
281
            pass
282
        else:
283
            self.fail("Should have raised AstakosClientException")
284

    
285
    def test_offline(self):
286
        """Test _offline without pool"""
287
        self._offline(False)
288

    
289
    def test_offline_pool(self):
290
        """Test _offline using pool"""
291
        self._offline(True)
292

    
293
    # ----------------------------------
294
    # Test the response we get if we send invalid token
295
    def _invalid_token(self, pool):
296
        token = "skaksaFlBl+fasFdaf24sx=="
297
        _mock_request([_request_ok])
298
        try:
299
            client = AstakosClient("https://example.com", use_pool=pool)
300
            client._call_astakos(token, "/im/authenticate")
301
        except Unauthorized:
302
            pass
303
        except Exception:
304
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
305
        else:
306
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
307

    
308
    def test_invalid_token(self):
309
        """Test _invalid_token without pool"""
310
        self._invalid_token(False)
311

    
312
    def test_invalid_token_pool(self):
313
        """Test _invalid_token using pool"""
314
        self._invalid_token(True)
315

    
316
    # ----------------------------------
317
    # Test the response we get if we send invalid url
318
    def _invalid_url(self, pool):
319
        global token_1
320
        _mock_request([_request_ok])
321
        try:
322
            client = AstakosClient("https://example.com", use_pool=pool)
323
            client._call_astakos(token_1, "/im/misspelled")
324
        except NotFound:
325
            pass
326
        except Exception:
327
            self.fail("Should have returned 404 (Not Found)")
328
        else:
329
            self.fail("Should have returned 404 (Not Found)")
330

    
331
    def test_invalid_url(self):
332
        """Test _invalid_url without pool"""
333
        self._invalid_url(False)
334

    
335
    def test_invalid_url_pool(self):
336
        """Test _invalid_url using pool"""
337
        self._invalid_url(True)
338

    
339
    # ----------------------------------
340
    # Test the response we get if we use an unsupported scheme
341
    def _unsupported_scheme(self, pool):
342
        global token_1
343
        _mock_request([_request_ok])
344
        try:
345
            client = AstakosClient("ftp://example.com", use_pool=pool)
346
            client._call_astakos(token_1, "/im/authenticate")
347
        except ValueError:
348
            pass
349
        except Exception:
350
            self.fail("Should have raise ValueError Exception")
351
        else:
352
            self.fail("Should have raise ValueError Exception")
353

    
354
    def test_unsupported_scheme(self):
355
        """Test _unsupported_scheme without pool"""
356
        self._unsupported_scheme(False)
357

    
358
    def test_unsupported_scheme_pool(self):
359
        """Test _unsupported_scheme using pool"""
360
        self._unsupported_scheme(True)
361

    
362
    # ----------------------------------
363
    # Test the response we get if we use http instead of https
364
    def _http_scheme(self, pool):
365
        global token_1
366
        _mock_request([_request_ok])
367
        try:
368
            client = AstakosClient("http://example.com", use_pool=pool)
369
            client._call_astakos(token_1, "/im/authenticate")
370
        except AstakosClientException as err:
371
            if err.status != 302:
372
                self.fail("Should have returned 302 (Found)")
373
        else:
374
            self.fail("Should have returned 302 (Found)")
375

    
376
    def test_http_scheme(self):
377
        """Test _http_scheme without pool"""
378
        self._http_scheme(False)
379

    
380
    def test_http_scheme_pool(self):
381
        """Test _http_scheme using pool"""
382
        self._http_scheme(True)
383

    
384
    # ----------------------------------
385
    # Test the response we get if we use authenticate with POST
386
    def _post_authenticate(self, pool):
387
        global token_1
388
        _mock_request([_request_ok])
389
        try:
390
            client = AstakosClient("https://example.com", use_pool=pool)
391
            client._call_astakos(token_1, "/im/authenticate", method="POST")
392
        except BadRequest:
393
            pass
394
        except Exception:
395
            self.fail("Should have returned 400 (Method not allowed)")
396
        else:
397
            self.fail("Should have returned 400 (Method not allowed)")
398

    
399
    def test_post_authenticate(self):
400
        """Test _post_authenticate without pool"""
401
        self._post_authenticate(False)
402

    
403
    def test_post_authenticate_pool(self):
404
        """Test _post_authenticate using pool"""
405
        self._post_authenticate(True)
406

    
407
    # ----------------------------------
408
    # Test the response if we request user_catalogs with GET
409
    def _get_user_catalogs(self, pool):
410
        global token_1
411
        _mock_request([_request_ok])
412
        try:
413
            client = AstakosClient("https://example.com", use_pool=pool)
414
            client._call_astakos(token_1, "/user_catalogs")
415
        except BadRequest:
416
            pass
417
        except Exception:
418
            self.fail("Should have returned 400 (Method not allowed)")
419
        else:
420
            self.fail("Should have returned 400 (Method not allowed)")
421

    
422
    def test_get_user_catalogs(self):
423
        """Test _get_user_catalogs without pool"""
424
        self._get_user_catalogs(False)
425

    
426
    def test_get_user_catalogs_pool(self):
427
        """Test _get_user_catalogs using pool"""
428
        self._get_user_catalogs(True)
429

    
430

    
431
class TestAuthenticate(unittest.TestCase):
432
    """Test cases for function getUserInfo"""
433

    
434
    # ----------------------------------
435
    # Test the response we get if we don't have internet access
436
    def test_offline(self):
437
        """Test offline after 3 retries"""
438
        global token_1
439
        _mock_request([_request_offline])
440
        try:
441
            client = AstakosClient("https://example.com", retry=3)
442
            client.get_user_info(token_1)
443
        except AstakosClientException:
444
            pass
445
        else:
446
            self.fail("Should have raised AstakosClientException exception")
447

    
448
    # ----------------------------------
449
    # Test the response we get for invalid token
450
    def _invalid_token(self, pool):
451
        token = "skaksaFlBl+fasFdaf24sx=="
452
        _mock_request([_request_ok])
453
        try:
454
            client = AstakosClient("https://example.com", use_pool=pool)
455
            client.get_user_info(token)
456
        except Unauthorized:
457
            pass
458
        except Exception:
459
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
460
        else:
461
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
462

    
463
    def test_invalid_token(self):
464
        """Test _invalid_token without pool"""
465
        self._invalid_token(False)
466

    
467
    def test_invalid_token_pool(self):
468
        """Test _invalid_token using pool"""
469
        self._invalid_token(True)
470

    
471
    #- ---------------------------------
472
    # Test response for user 1
473
    def _auth_user(self, token, user_info, usage, pool):
474
        _mock_request([_request_ok])
475
        try:
476
            client = AstakosClient("https://example.com", use_pool=pool)
477
            auth_info = client.get_user_info(token, usage=usage)
478
        except:
479
            self.fail("Shouldn't raise an Exception")
480
        self.assertEqual(user_info, auth_info)
481

    
482
    def test_auth_user_one(self):
483
        """Test _auth_user for User 1 without pool, without usage"""
484
        global token_1, user_1
485
        user_info = dict(user_1)
486
        del user_info['usage']
487
        self._auth_user(token_1, user_info, False, False)
488

    
489
    def test_auth_user_one_usage(self):
490
        """Test _auth_user for User 1 without pool, with usage"""
491
        global token_1, user_1
492
        self._auth_user(token_1, user_1, True, False)
493

    
494
    def test_auth_user_one_usage_pool(self):
495
        """Test _auth_user for User 1 using pool, with usage"""
496
        global token_1, user_1
497
        self._auth_user(token_1, user_1, True, True)
498

    
499
    def test_auth_user_two(self):
500
        """Test _auth_user for User 2 without pool, without usage"""
501
        global token_2, user_2
502
        user_info = dict(user_2)
503
        del user_info['usage']
504
        self._auth_user(token_2, user_info, False, False)
505

    
506
    def test_auth_user_two_usage(self):
507
        """Test _auth_user for User 2 without pool, with usage"""
508
        global token_2, user_2
509
        self._auth_user(token_2, user_2, True, False)
510

    
511
    def test_auth_user_two_usage_pool(self):
512
        """Test _auth_user for User 2 using pool, with usage"""
513
        global token_2, user_2
514
        self._auth_user(token_2, user_2, True, True)
515

    
516
    # ----------------------------------
517
    # Test retry functionality
518
    def test_offline_retry(self):
519
        """Test retry functionality for getUserInfo"""
520
        global token_1, user_1
521
        _mock_request([_request_offline, _request_offline, _request_ok])
522
        try:
523
            client = AstakosClient("https://example.com", retry=2)
524
            auth_info = client.get_user_info(token_1, usage=True)
525
        except:
526
            self.fail("Shouldn't raise an Exception")
527
        self.assertEqual(user_1, auth_info)
528

    
529

    
530
class TestDisplayNames(unittest.TestCase):
531
    """Test cases for functions getDisplayNames/getDisplayName"""
532

    
533
    # ----------------------------------
534
    # Test the response we get for invalid token
535
    def test_invalid_token(self):
536
        """Test the response we get for invalid token (without pool)"""
537
        global user_1
538
        token = "skaksaFlBl+fasFdaf24sx=="
539
        _mock_request([_request_ok])
540
        try:
541
            client = AstakosClient("https://example.com")
542
            client.get_usernames(token, [user_1['uuid']])
543
        except Unauthorized:
544
            pass
545
        except Exception:
546
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
547
        else:
548
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
549

    
550
    # ----------------------------------
551
    # Get Info for both users
552
    def test_usernames(self):
553
        """Test get_usernames with both users"""
554
        global token_1, user_1, user_2
555
        _mock_request([_request_ok])
556
        try:
557
            client = AstakosClient("https://example.com")
558
            catalog = client.get_usernames(
559
                token_1, [user_1['uuid'], user_2['uuid']])
560
        except:
561
            self.fail("Shouldn't raise an Exception")
562
        self.assertEqual(catalog[user_1['uuid']], user_1['username'])
563
        self.assertEqual(catalog[user_2['uuid']], user_2['username'])
564

    
565
    # ----------------------------------
566
    # Get info for user 1
567
    def test_username_user_one(self):
568
        """Test get_username for User One"""
569
        global token_2, user_1
570
        _mock_request([_request_offline, _request_ok])
571
        try:
572
            client = AstakosClient(
573
                "https://example.com", use_pool=True, retry=2)
574
            info = client.get_username(token_2, user_1['uuid'])
575
        except:
576
            self.fail("Shouldn't raise an Exception")
577
        self.assertEqual(info, user_1['username'])
578

    
579
    # ----------------------------------
580
    # Get info with wrong uuid
581
    def test_no_username(self):
582
        global token_1
583
        _mock_request([_request_ok])
584
        try:
585
            client = AstakosClient("https://example.com")
586
            client.get_username(token_1, "1234")
587
        except NoUserName:
588
            pass
589
        except:
590
            self.fail("Should have raised NoDisplayName exception")
591
        else:
592
            self.fail("Should have raised NoDisplayName exception")
593

    
594

    
595
class TestGetUUIDs(unittest.TestCase):
596
    """Test cases for functions getUUIDs/getUUID"""
597

    
598
    # ----------------------------------
599
    # Test the response we get for invalid token
600
    def test_invalid_token(self):
601
        """Test the response we get for invalid token (using pool)"""
602
        global user_1
603
        token = "skaksaFlBl+fasFdaf24sx=="
604
        _mock_request([_request_ok])
605
        try:
606
            client = AstakosClient("https://example.com")
607
            client.get_uuids(token, [user_1['username']])
608
        except Unauthorized:
609
            pass
610
        except Exception:
611
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
612
        else:
613
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
614

    
615
    # ----------------------------------
616
    # Get info for both users
617
    def test_uuids(self):
618
        """Test get_uuids with both users"""
619
        global token_1, user_1, user_2
620
        _mock_request([_request_ok])
621
        try:
622
            client = AstakosClient("https://example.com")
623
            catalog = client.get_uuids(
624
                token_1, [user_1['username'], user_2['username']])
625
        except:
626
            self.fail("Shouldn't raise an Exception")
627
        self.assertEqual(catalog[user_1['username']], user_1['uuid'])
628
        self.assertEqual(catalog[user_2['username']], user_2['uuid'])
629

    
630
    # ----------------------------------
631
    # Get uuid for user 2
632
    def test_get_uuid_user_two(self):
633
        """Test get_uuid for User Two"""
634
        global token_1, user_2
635
        _mock_request([_request_offline, _request_ok])
636
        try:
637
            client = AstakosClient("https://example.com", retry=1)
638
            info = client.get_uuid(token_2, user_1['username'])
639
        except:
640
            self.fail("Shouldn't raise an Exception")
641
        self.assertEqual(info, user_1['uuid'])
642

    
643
    # ----------------------------------
644
    # Get uuid with wrong username
645
    def test_no_uuid(self):
646
        global token_1
647
        _mock_request([_request_ok])
648
        try:
649
            client = AstakosClient("https://example.com")
650
            client.get_uuid(token_1, "1234")
651
        except NoUUID:
652
            pass
653
        except:
654
            self.fail("Should have raised NoUUID exception")
655
        else:
656
            self.fail("Should have raised NoUUID exception")
657

    
658

    
659
# ----------------------------
660
# Run tests
661
if __name__ == "__main__":
662
    unittest.main()