Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-client / astakosclient / tests.py @ f93cc364

History | View | Annotate | Download (21.4 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

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

    
60

    
61
# --------------------------------------------------------------------
62
# Helper functions
63

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

    
70

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

    
79

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

    
89

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

    
96

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

    
103

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

    
113

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

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

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

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

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

    
140

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

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

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

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

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

    
180

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

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

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

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

    
203

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

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

    
264

    
265
# --------------------------------------------------------------------
266
# The actual tests
267

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

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

    
284
    def test_Offline(self):
285
        """Test _offline without pool"""
286
        self._offline(False)
287

    
288
    def test_OfflinePool(self):
289
        """Test _offline using pool"""
290
        self._offline(True)
291

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

    
307
    def test_InvalidToken(self):
308
        """Test _invalidToken without pool"""
309
        self._invalidToken(False)
310

    
311
    def test_InvalidTokenPool(self):
312
        """Test _invalidToken using pool"""
313
        self._invalidToken(True)
314

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

    
330
    def test_InvalidUrl(self):
331
        """Test _invalidUrl without pool"""
332
        self._invalidUrl(False)
333

    
334
    def test_invalidUrlPool(self):
335
        """Test _invalidUrl using pool"""
336
        self._invalidUrl(True)
337

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

    
353
    def test_UnsupportedScheme(self):
354
        """Test _unsupportedScheme without pool"""
355
        self._unsupportedScheme(False)
356

    
357
    def test_UnsupportedSchemePool(self):
358
        """Test _unsupportedScheme using pool"""
359
        self._unsupportedScheme(True)
360

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

    
375
    def test_HttpScheme(self):
376
        """Test _httpScheme without pool"""
377
        self._httpScheme(False)
378

    
379
    def test_HttpSchemePool(self):
380
        """Test _httpScheme using pool"""
381
        self._httpScheme(True)
382

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

    
398
    def test_PostAuthenticate(self):
399
        """Test _postAuthenticate without pool"""
400
        self._postAuthenticate(False)
401

    
402
    def test_PostAuthenticatePool(self):
403
        """Test _postAuthenticate using pool"""
404
        self._postAuthenticate(True)
405

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

    
421
    def test_GetUserCatalogs(self):
422
        """Test _getUserCatalogs without pool"""
423
        self._getUserCatalogs(False)
424

    
425
    def test_GetUserCatalogsPool(self):
426
        """Test _getUserCatalogs using pool"""
427
        self._getUserCatalogs(True)
428

    
429

    
430
class TestAuthenticate(unittest.TestCase):
431
    """Test cases for function authenticate"""
432

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

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

    
462
    def test_InvalidToken(self):
463
        """Test _invalidToken without pool"""
464
        self._invalidToken(False)
465

    
466
    def test_InvalidTokenPool(self):
467
        """Test _invalidToken using pool"""
468
        self._invalidToken(True)
469

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

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

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

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

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

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

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

    
515
    # ----------------------------------
516
    # Test retry functionality
517
    def test_OfflineRetry(self):
518
        """Test retry functionality for authentication"""
519
        global token_1, user_1
520
        _mockRequest([_requestOffline, _requestOffline, _requestOk])
521
        try:
522
            client = AstakosClient("https://example.com", retry=2)
523
            auth_info = client.authenticate(token_1, usage=True)
524
        except:
525
            self.fail("Shouldn't raise an Exception")
526
        self.assertEqual(user_1, auth_info)
527

    
528

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

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

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

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

    
578

    
579
class TestGetUUIDs(unittest.TestCase):
580
    """Test cases for functions getUUIDs/getUUID"""
581

    
582
    # ----------------------------------
583
    # Test the response we get for invalid token
584
    def test_InvalidToken(self):
585
        """Test the response we get for invalid token (using pool)"""
586
        global user_1
587
        token = "skaksaFlBl+fasFdaf24sx=="
588
        _mockRequest([_requestOk])
589
        try:
590
            client = AstakosClient("https://example.com")
591
            client.getUUIDs(token, [user_1['username']])
592
        except Unauthorized:
593
            pass
594
        except Exception:
595
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
596
        else:
597
            self.fail("Should have returned 401 (Invalid X-Auth-Token)")
598

    
599
    # ----------------------------------
600
    # Get info for both users
601
    def test_UUIDs(self):
602
        """Test getUUIDs with both users"""
603
        global token_1, user_1, user_2
604
        _mockRequest([_requestOk])
605
        try:
606
            client = AstakosClient("https://example.com")
607
            catalog = client.getUUIDs(
608
                token_1, [user_1['username'], user_2['username']])
609
        except:
610
            self.fail("Shouldn't raise an Exception")
611
        self.assertEqual(catalog[user_1['username']], user_1['uuid'])
612
        self.assertEqual(catalog[user_2['username']], user_2['uuid'])
613

    
614
    # ----------------------------------
615
    # Get uuid for user 2
616
    def test_GetUUIDUserTwo(self):
617
        """Test getUUID for User Two"""
618
        global token_1, user_2
619
        _mockRequest([_requestOffline, _requestOk])
620
        try:
621
            client = AstakosClient("https://example.com", retry=1)
622
            info = client.getUUID(token_2, user_1['username'])
623
        except:
624
            self.fail("Shouldn't raise an Exception")
625
        self.assertEqual(info, user_1['uuid'])
626

    
627

    
628
# ----------------------------
629
# Run tests
630
if __name__ == "__main__":
631
    unittest.main()