Statistics
| Branch: | Tag: | Revision:

root / astakosclient / astakosclient / tests.py @ 21190887

History | View | Annotate | Download (22.6 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
    message = "FOUND"
75
    status = 302
76
    data = '<html>\r\n<head><title>302 Found</title></head>\r\n' \
77
        '<body bgcolor="white">\r\n<center><h1>302 Found</h1></center>\r\n' \
78
        '<hr><center>nginx/0.7.67</center>\r\n</body>\r\n</html>\r\n'
79
    return (message, data, status)
80

    
81

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

    
92

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

    
100

    
101
def _request_status_400(conn, method, url, **kwargs):
102
    """This request returns 400"""
103
    message = "BAD REQUEST"
104
    status = 400
105
    data = "Method not allowed.\n"
106
    return (message, data, status)
107

    
108

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

    
118

    
119
def _req_authenticate(conn, method, url, **kwargs):
120
    """Check if user exists and return his profile"""
121
    global user_1, user_2
122

    
123
    # Check input
124
    if conn.__class__.__name__ != "HTTPSConnection":
125
        return _request_status_302(conn, method, url, **kwargs)
126

    
127
    if method != "GET":
128
        return _request_status_400(conn, method, url, **kwargs)
129

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

    
139
    # Return
140
    if "usage=1" not in url:
141
        # Strip `usage' key from `user'
142
        del user['usage']
143
    return ("", simplejson.dumps(user), 200)
144

    
145

    
146
def _req_catalogs(conn, method, url, **kwargs):
147
    """Return user catalogs"""
148
    global token_1, token_2, user_1, user_2
149

    
150
    # Check input
151
    if conn.__class__.__name__ != "HTTPSConnection":
152
        return _request_status_302(conn, method, url, **kwargs)
153

    
154
    if method != "POST":
155
        return _request_status_400(conn, method, url, **kwargs)
156

    
157
    token = kwargs['headers']['X-Auth-Token']
158
    if token != token_1 and token != token_2:
159
        return _request_status_401(conn, method, url, **kwargs)
160

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

    
185

    
186
# ----------------------------
187
# Mock the actual _doRequest
188
def _mock_request(new_requests):
189
    """Mock the actual request
190

191
    Given a list of requests to use (in rotation),
192
    replace the original _doRequest function with
193
    a new one
194

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

    
204
    _mock.requests = new_requests
205
    # Replace `_doRequest' with our `_mock'
206
    astakosclient._do_request = _mock
207

    
208

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

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

    
269

    
270
# --------------------------------------------------------------------
271
# The actual tests
272

    
273
class TestCallAstakos(unittest.TestCase):
274
    """Test cases for function _callAstakos"""
275

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

    
289
    def test_offline(self):
290
        """Test _offline without pool"""
291
        self._offline(False)
292

    
293
    def test_offline_pool(self):
294
        """Test _offline using pool"""
295
        self._offline(True)
296

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

    
312
    def test_invalid_token(self):
313
        """Test _invalid_token without pool"""
314
        self._invalid_token(False)
315

    
316
    def test_invalid_token_pool(self):
317
        """Test _invalid_token using pool"""
318
        self._invalid_token(True)
319

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

    
335
    def test_invalid_url(self):
336
        """Test _invalid_url without pool"""
337
        self._invalid_url(False)
338

    
339
    def test_invalid_url_pool(self):
340
        """Test _invalid_url using pool"""
341
        self._invalid_url(True)
342

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

    
358
    def test_unsupported_scheme(self):
359
        """Test _unsupported_scheme without pool"""
360
        self._unsupported_scheme(False)
361

    
362
    def test_unsupported_scheme_pool(self):
363
        """Test _unsupported_scheme using pool"""
364
        self._unsupported_scheme(True)
365

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

    
380
    def test_http_scheme(self):
381
        """Test _http_scheme without pool"""
382
        self._http_scheme(False)
383

    
384
    def test_http_scheme_pool(self):
385
        """Test _http_scheme using pool"""
386
        self._http_scheme(True)
387

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

    
403
    def test_post_authenticate(self):
404
        """Test _post_authenticate without pool"""
405
        self._post_authenticate(False)
406

    
407
    def test_post_authenticate_pool(self):
408
        """Test _post_authenticate using pool"""
409
        self._post_authenticate(True)
410

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

    
426
    def test_get_user_catalogs(self):
427
        """Test _get_user_catalogs without pool"""
428
        self._get_user_catalogs(False)
429

    
430
    def test_get_user_catalogs_pool(self):
431
        """Test _get_user_catalogs using pool"""
432
        self._get_user_catalogs(True)
433

    
434

    
435
class TestAuthenticate(unittest.TestCase):
436
    """Test cases for function getUserInfo"""
437

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

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

    
467
    def test_invalid_token(self):
468
        """Test _invalid_token without pool"""
469
        self._invalid_token(False)
470

    
471
    def test_invalid_token_pool(self):
472
        """Test _invalid_token using pool"""
473
        self._invalid_token(True)
474

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

    
486
    def test_auth_user_one(self):
487
        """Test _auth_user for User 1 without pool, without usage"""
488
        global token_1, user_1
489
        user_info = dict(user_1)
490
        del user_info['usage']
491
        self._auth_user(token_1, user_info, False, False)
492

    
493
    def test_auth_user_one_usage(self):
494
        """Test _auth_user for User 1 without pool, with usage"""
495
        global token_1, user_1
496
        self._auth_user(token_1, user_1, True, False)
497

    
498
    def test_auth_user_one_usage_pool(self):
499
        """Test _auth_user for User 1 using pool, with usage"""
500
        global token_1, user_1
501
        self._auth_user(token_1, user_1, True, True)
502

    
503
    def test_auth_user_two(self):
504
        """Test _auth_user for User 2 without pool, without usage"""
505
        global token_2, user_2
506
        user_info = dict(user_2)
507
        del user_info['usage']
508
        self._auth_user(token_2, user_info, False, False)
509

    
510
    def test_auth_user_two_usage(self):
511
        """Test _auth_user for User 2 without pool, with usage"""
512
        global token_2, user_2
513
        self._auth_user(token_2, user_2, True, False)
514

    
515
    def test_auth_user_two_usage_pool(self):
516
        """Test _auth_user for User 2 using pool, with usage"""
517
        global token_2, user_2
518
        self._auth_user(token_2, user_2, True, True)
519

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

    
533

    
534
class TestDisplayNames(unittest.TestCase):
535
    """Test cases for functions getDisplayNames/getDisplayName"""
536

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

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

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

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

    
598

    
599
class TestGetUUIDs(unittest.TestCase):
600
    """Test cases for functions getUUIDs/getUUID"""
601

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

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

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

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

    
662

    
663
# ----------------------------
664
# Run tests
665
if __name__ == "__main__":
666
    unittest.main()