Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-client / astakosclient / tests.py @ 8fe6475a

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, AstakosClientException, \
49
    AstakosClientEInvalid, AstakosClientEMethod, AstakosClientENotFound
50

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

    
59

    
60
# --------------------------------------------------------------------
61
# Helper functions
62

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

    
69

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

    
78

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

    
88

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

    
95

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

    
102

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

    
112

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

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

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

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

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

    
139

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

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

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

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

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

    
179

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

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

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

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

    
202

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

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

    
263

    
264
# --------------------------------------------------------------------
265
# The actual tests
266

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
428

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

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

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

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

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

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

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

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

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

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

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

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

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

    
527

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

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

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

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

    
577

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

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

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

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

    
626

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