Browse Source

Merge pull request #56 from tracim/fix/better_session_api

Damien Accorsi 6 years ago
parent
commit
91e1f68254
No account linked to committer's email

+ 9 - 5
tracim/__init__.py View File

@@ -13,8 +13,9 @@ from tracim.lib.utils.authentification import basic_auth_check_credentials
13 13
 from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
14 14
 from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
15 15
 from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
16
+from tracim.views import BASE_API_V2
16 17
 from tracim.views.core_api.session_controller import SessionController
17
-from tracim.views.default.default_controller import DefaultController
18
+from tracim.views.errors import ErrorSchema
18 19
 
19 20
 
20 21
 def main(global_config, **settings):
@@ -44,12 +45,15 @@ def main(global_config, **settings):
44 45
     # Add SqlAlchemy DB
45 46
     configurator.include('.models')
46 47
     # set Hapic
47
-    hapic.set_context(PyramidContext(configurator))
48
+    hapic.set_context(
49
+        PyramidContext(
50
+            configurator=configurator,
51
+            default_error_builder=ErrorSchema()
52
+        )
53
+    )
48 54
     # Add controllers
49
-    default_controllers = DefaultController()
50
-    default_controllers.bind(configurator)
51 55
     session_api = SessionController()
52
-    session_api.bind(configurator)
56
+    configurator.include(session_api.bind, route_prefix=BASE_API_V2)
53 57
     hapic.add_documentation_view(
54 58
         '/api/v2/doc',
55 59
         'Tracim v2 API',

+ 19 - 3
tracim/tests/__init__.py View File

@@ -27,10 +27,14 @@ def eq_(a, b, msg=None):
27 27
 
28 28
 
29 29
 class FunctionalTest(unittest.TestCase):
30
+
31
+    fixtures = [BaseFixture]
32
+    sqlalchemy_url = 'sqlite:///tracim_test.sqlite'
33
+
30 34
     def setUp(self):
31 35
         DepotManager._clear()
32 36
         settings = {
33
-            'sqlalchemy.url': 'sqlite:///tracim_test.sqlite',
37
+            'sqlalchemy.url': self.sqlalchemy_url,
34 38
             'user.auth_token.validity': '604800',
35 39
             'depot_storage_dir': '/tmp/test/depot',
36 40
             'depot_storage_name': 'test',
@@ -51,7 +55,7 @@ class FunctionalTest(unittest.TestCase):
51 55
             dbsession = get_tm_session(session_factory, transaction.manager)
52 56
             try:
53 57
                 fixtures_loader = FixturesLoader(dbsession, app_config)
54
-                fixtures_loader.loads([BaseFixture])
58
+                fixtures_loader.loads(self.fixtures)
55 59
                 transaction.commit()
56 60
                 print("Database initialized.")
57 61
             except IntegrityError:
@@ -72,10 +76,22 @@ class FunctionalTest(unittest.TestCase):
72 76
         DepotManager._clear()
73 77
 
74 78
 
79
+class FunctionalTestEmptyDB(FunctionalTest):
80
+    fixtures = []
81
+
82
+
83
+class FunctionalTestNoDB(FunctionalTest):
84
+    sqlalchemy_url = 'sqlite://'
85
+
86
+    def init_database(self, settings):
87
+        self.engine = get_engine(settings)
88
+
89
+
75 90
 class BaseTest(unittest.TestCase):
76 91
     """
77 92
     Pyramid default test.
78 93
     """
94
+
79 95
     def setUp(self):
80 96
         logger.debug(self, 'Setup Test...')
81 97
         self.config = testing.setUp(settings={
@@ -97,7 +113,7 @@ class BaseTest(unittest.TestCase):
97 113
             get_engine,
98 114
             get_session_factory,
99 115
             get_tm_session,
100
-            )
116
+        )
101 117
 
102 118
         self.engine = get_engine(settings)
103 119
         session_factory = get_session_factory(self.engine)

+ 3 - 3
tracim/tests/functional/test_doc.py View File

@@ -3,15 +3,15 @@ from tracim.tests import FunctionalTest
3 3
 
4 4
 class TestDoc(FunctionalTest):
5 5
 
6
-    def test_index(self):
6
+    def test_api__check_doc_index_html_page__ok_200__nominal_case(self):
7 7
         res = self.testapp.get('/api/v2/doc/', status=200)
8 8
         assert res.content_type == 'text/html'
9 9
 
10
-    def test_spec_yml(self):
10
+    def test_api__check_spec_yaml_file__ok_200__nominal_case(self):
11 11
         res = self.testapp.get('/api/v2/doc/spec.yml', status=200)
12 12
         assert res.content_type == 'text/x-yaml'
13 13
 
14
-    def test_assets(self):
14
+    def test_api__check_docs_assets__ok_200__nominal_case(self):
15 15
         res = self.testapp.get(
16 16
             '/api/v2/doc/favicon-32x32.png',
17 17
             status=200,

+ 53 - 12
tracim/tests/functional/test_session.py View File

@@ -1,57 +1,94 @@
1 1
 # coding=utf-8
2 2
 import pytest
3
+from sqlalchemy.exc import OperationalError
3 4
 
4 5
 from tracim.tests import FunctionalTest
6
+from tracim.tests import FunctionalTestNoDB
5 7
 
6 8
 
7 9
 class TestLogoutEndpoint(FunctionalTest):
8 10
 
9
-    def test_logout(self):
11
+    def test_api__access_logout_get_enpoint__ok__nominal_case(self):
12
+        res = self.testapp.post_json('/api/v2/sessions/logout', status=204)
13
+
14
+    def test_api__access_logout_post_enpoint__ok__nominal_case(self):
10 15
         res = self.testapp.get('/api/v2/sessions/logout', status=204)
11 16
 
12 17
 
18
+class TestLoginEndpointUnititedDB(FunctionalTestNoDB):
19
+
20
+    @pytest.mark.xfail(raises=OperationalError,
21
+                       reason='Not supported yet by hapic')
22
+    def test_api__try_login_enpoint__err_500__no_inited_db(self):
23
+        params = {
24
+            'email': 'admin@admin.admin',
25
+            'password': 'admin@admin.admin',
26
+        }
27
+        res = self.testapp.post_json(
28
+            '/api/v2/sessions/login',
29
+            params=params,
30
+            status=500,
31
+        )
32
+        assert isinstance(res.json, dict)
33
+        assert 'code' in res.json.keys()
34
+        assert 'message' in res.json.keys()
35
+        assert 'details' in res.json.keys()
36
+
37
+
13 38
 class TestLoginEndpoint(FunctionalTest):
14 39
 
15
-    def test_login_ok(self):
40
+    def test_api__try_login_enpoint__ok_204__nominal_case(self):
16 41
         params = {
17 42
             'email': 'admin@admin.admin',
18 43
             'password': 'admin@admin.admin',
19 44
         }
20
-        res = self.testapp.get(
45
+        res = self.testapp.post_json(
21 46
             '/api/v2/sessions/login',
47
+            params=params,
22 48
             status=204,
23
-            params=params
24 49
         )
25 50
 
26
-    def test_bad_password(self):
51
+    def test_api__try_login_enpoint__err_400__bad_password(self):
27 52
         params = {
28 53
             'email': 'admin@admin.admin',
29 54
             'password': 'bad_password',
30 55
         }
31
-        res = self.testapp.get(
56
+        res = self.testapp.post_json(
32 57
             '/api/v2/sessions/login',
33 58
             status=400,
34 59
             params=params,
35 60
         )
61
+        assert isinstance(res.json, dict)
62
+        assert 'code' in res.json.keys()
63
+        assert 'message' in res.json.keys()
64
+        assert 'details' in res.json.keys()
36 65
 
37
-    def test_bad_user(self):
66
+    def test_api__try_login_enpoint__err_400__unregistered_user(self):
38 67
         params = {
39 68
             'email': 'unknown_user@unknown.unknown',
40 69
             'password': 'bad_password',
41 70
         }
42
-        res = self.testapp.get(
71
+        res = self.testapp.post_json(
43 72
             '/api/v2/sessions/login',
44 73
             status=400,
45 74
             params=params,
46 75
         )
76
+        assert isinstance(res.json, dict)
77
+        assert 'code' in res.json.keys()
78
+        assert 'message' in res.json.keys()
79
+        assert 'details' in res.json.keys()
47 80
 
48
-    def test_uncomplete(self):
49
-        res = self.testapp.get('/api/v2/sessions/login', status=400)
81
+    def test_api__try_login_enpoint__err_400__no_json_body(self):
82
+        res = self.testapp.post_json('/api/v2/sessions/login', status=400)
83
+        assert isinstance(res.json, dict)
84
+        assert 'code' in res.json.keys()
85
+        assert 'message' in res.json.keys()
86
+        assert 'details' in res.json.keys()
50 87
 
51 88
 
52 89
 class TestWhoamiEndpoint(FunctionalTest):
53 90
 
54
-    def test_whoami_ok(self):
91
+    def test_api__try_whoami_enpoint__ok_200__nominal_case(self):
55 92
         self.testapp.authorization = (
56 93
             'Basic',
57 94
             (
@@ -70,7 +107,7 @@ class TestWhoamiEndpoint(FunctionalTest):
70 107
         assert res.json_body['caldav_url'] is None
71 108
         assert res.json_body['avatar_url'] is None
72 109
 
73
-    def test_unauthenticated(self):
110
+    def test_api__try_whoami_enpoint__err_401__unauthenticated(self):
74 111
         self.testapp.authorization = (
75 112
             'Basic',
76 113
             (
@@ -79,3 +116,7 @@ class TestWhoamiEndpoint(FunctionalTest):
79 116
             )
80 117
         )
81 118
         res = self.testapp.get('/api/v2/sessions/whoami', status=401)
119
+        assert isinstance(res.json, dict)
120
+        assert 'code' in res.json.keys()
121
+        assert 'message' in res.json.keys()
122
+        assert 'details' in res.json.keys()

+ 21 - 48
tracim/views/core_api/session_controller.py View File

@@ -1,35 +1,30 @@
1 1
 # coding=utf-8
2
-import os
3
-from http.client import HTTPException
4
-
5
-from pyramid.httpexceptions import HTTPNoContent
6
-from pyramid.response import Response
2
+from pyramid.config import Configurator
7 3
 from sqlalchemy.orm.exc import NoResultFound
4
+try:  # Python 3.5+
5
+    from http import HTTPStatus
6
+except ImportError:
7
+    from http import client as HTTPStatus
8
+
8 9
 
9 10
 from tracim import TracimRequest
10 11
 from tracim.extensions import hapic
11 12
 from tracim.lib.core.user import UserApi
12 13
 from tracim.models.context_models import UserInContext
13 14
 from tracim.views.controllers import Controller
14
-from pyramid.config import Configurator
15
-
16
-from tracim.views import BASE_API_V2
17
-from tracim.views.core_api.schemas import UserSchema, NoContentSchema
15
+from tracim.views.core_api.schemas import UserSchema
16
+from tracim.views.core_api.schemas import NoContentSchema
18 17
 from tracim.views.core_api.schemas import LoginOutputHeaders
19 18
 from tracim.views.core_api.schemas import BasicAuthSchema
20
-from tracim.exceptions import NotAuthentificated, LoginFailed
21
-
22
-try:  # Python 3.5+
23
-    from http import HTTPStatus
24
-except ImportError:
25
-    from http import client as HTTPStatus
19
+from tracim.exceptions import NotAuthentificated
20
+from tracim.exceptions import LoginFailed
26 21
 
27 22
 
28 23
 class SessionController(Controller):
29 24
 
30 25
     @hapic.with_api_doc()
31 26
     @hapic.input_headers(LoginOutputHeaders())
32
-    @hapic.input_query(BasicAuthSchema())
27
+    @hapic.input_body(BasicAuthSchema())
33 28
     @hapic.handle_exception(LoginFailed, http_code=HTTPStatus.BAD_REQUEST)
34 29
     # TODO - G.M - 17-04-2018 - fix output header ?
35 30
     # @hapic.output_headers()
@@ -41,10 +36,8 @@ class SessionController(Controller):
41 36
         """
42 37
         Logs user into the system
43 38
         """
44
-        email = request.params['email']
45
-        password = request.params['password']
46
-        if not (email and password):
47
-            raise Exception
39
+        email = request.json_body['email']
40
+        password = request.json_body['password']
48 41
         app_config = request.registry.settings['CFG']
49 42
         try:
50 43
             uapi = UserApi(
@@ -96,33 +89,13 @@ class SessionController(Controller):
96 89
     def bind(self, configurator: Configurator):
97 90
 
98 91
         # Login
99
-        configurator.add_route(
100
-            'login',
101
-            os.path.join(BASE_API_V2, 'sessions', 'login'),
102
-            request_method='GET'
103
-        )
104
-        configurator.add_view(
105
-            self.login,
106
-            route_name='login',
107
-        )
92
+        configurator.add_route('login', '/sessions/login', request_method='POST')  # nopep8
93
+        configurator.add_view(self.login, route_name='login')
108 94
         # Logout
109
-        configurator.add_route(
110
-            'logout',
111
-            os.path.join(BASE_API_V2, 'sessions', 'logout'),
112
-            request_method='GET'
113
-        )
114
-
115
-        configurator.add_view(
116
-            self.logout,
117
-            route_name='logout',
118
-        )
95
+        configurator.add_route('logout', '/sessions/logout', request_method='POST')  # nopep8
96
+        configurator.add_view(self.logout, route_name='logout')
97
+        configurator.add_route('logout_get', '/sessions/logout', request_method='GET')  # nopep8
98
+        configurator.add_view(self.logout, route_name='logout_get')
119 99
         # Whoami
120
-        configurator.add_route(
121
-            'whoami',
122
-            os.path.join(BASE_API_V2, 'sessions', 'whoami'),
123
-            request_method='GET'
124
-        )
125
-        configurator.add_view(
126
-            self.whoami,
127
-            route_name='whoami',
128
-        )
100
+        configurator.add_route('whoami', '/sessions/whoami', request_method='GET')  # nopep8
101
+        configurator.add_view(self.whoami, route_name='whoami',)

+ 0 - 0
tracim/views/default/__init__.py View File


+ 0 - 36
tracim/views/default/default_controller.py View File

@@ -1,36 +0,0 @@
1
-# coding=utf-8
2
-from tracim import TracimRequest
3
-from tracim.extensions import hapic
4
-from tracim.views.controllers import Controller
5
-from pyramid.config import Configurator
6
-from pyramid.exceptions import NotFound
7
-
8
-
9
-class DefaultController(Controller):
10
-
11
-    def notfound_view(self, request: TracimRequest):
12
-        request.response.status = 404
13
-        return {}
14
-
15
-    def swagger_doc(self, request: TracimRequest):
16
-        return hapic.generate_doc(
17
-                title='Tracim v2 API',
18
-                description='API of Tracim v2',
19
-        )
20
-
21
-    def bind(self, configurator: Configurator):
22
-        configurator.add_view(
23
-            self.notfound_view,
24
-            renderer='json',
25
-            context=NotFound,
26
-        )
27
-        configurator.add_route(
28
-            'swagger_doc',
29
-            '/swagger_doc',
30
-            request_method='GET',
31
-        )
32
-        configurator.add_view(
33
-            self.swagger_doc,
34
-            route_name='swagger_doc',
35
-            renderer='json',
36
-        )

+ 10 - 0
tracim/views/errors.py View File

@@ -0,0 +1,10 @@
1
+from hapic.error import DefaultErrorBuilder
2
+
3
+
4
+class ErrorSchema(DefaultErrorBuilder):
5
+    """
6
+    This class is both a builder and a Marshmallow Schema, His named is used for
7
+    swagger ui error schema. That's why we call it ErrorSchema To have
8
+    a nice naming in swagger ui.
9
+    """
10
+    pass