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
 from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
13
 from tracim.lib.utils.authentification import BASIC_AUTH_WEBUI_REALM
14
 from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
14
 from tracim.lib.utils.authorization import AcceptAllAuthorizationPolicy
15
 from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
15
 from tracim.lib.utils.authorization import TRACIM_DEFAULT_PERM
16
+from tracim.views import BASE_API_V2
16
 from tracim.views.core_api.session_controller import SessionController
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
 def main(global_config, **settings):
21
 def main(global_config, **settings):
44
     # Add SqlAlchemy DB
45
     # Add SqlAlchemy DB
45
     configurator.include('.models')
46
     configurator.include('.models')
46
     # set Hapic
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
     # Add controllers
54
     # Add controllers
49
-    default_controllers = DefaultController()
50
-    default_controllers.bind(configurator)
51
     session_api = SessionController()
55
     session_api = SessionController()
52
-    session_api.bind(configurator)
56
+    configurator.include(session_api.bind, route_prefix=BASE_API_V2)
53
     hapic.add_documentation_view(
57
     hapic.add_documentation_view(
54
         '/api/v2/doc',
58
         '/api/v2/doc',
55
         'Tracim v2 API',
59
         'Tracim v2 API',

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

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

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

3
 
3
 
4
 class TestDoc(FunctionalTest):
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
         res = self.testapp.get('/api/v2/doc/', status=200)
7
         res = self.testapp.get('/api/v2/doc/', status=200)
8
         assert res.content_type == 'text/html'
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
         res = self.testapp.get('/api/v2/doc/spec.yml', status=200)
11
         res = self.testapp.get('/api/v2/doc/spec.yml', status=200)
12
         assert res.content_type == 'text/x-yaml'
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
         res = self.testapp.get(
15
         res = self.testapp.get(
16
             '/api/v2/doc/favicon-32x32.png',
16
             '/api/v2/doc/favicon-32x32.png',
17
             status=200,
17
             status=200,

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

1
 # coding=utf-8
1
 # coding=utf-8
2
 import pytest
2
 import pytest
3
+from sqlalchemy.exc import OperationalError
3
 
4
 
4
 from tracim.tests import FunctionalTest
5
 from tracim.tests import FunctionalTest
6
+from tracim.tests import FunctionalTestNoDB
5
 
7
 
6
 
8
 
7
 class TestLogoutEndpoint(FunctionalTest):
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
         res = self.testapp.get('/api/v2/sessions/logout', status=204)
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
 class TestLoginEndpoint(FunctionalTest):
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
         params = {
41
         params = {
17
             'email': 'admin@admin.admin',
42
             'email': 'admin@admin.admin',
18
             'password': 'admin@admin.admin',
43
             'password': 'admin@admin.admin',
19
         }
44
         }
20
-        res = self.testapp.get(
45
+        res = self.testapp.post_json(
21
             '/api/v2/sessions/login',
46
             '/api/v2/sessions/login',
47
+            params=params,
22
             status=204,
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
         params = {
52
         params = {
28
             'email': 'admin@admin.admin',
53
             'email': 'admin@admin.admin',
29
             'password': 'bad_password',
54
             'password': 'bad_password',
30
         }
55
         }
31
-        res = self.testapp.get(
56
+        res = self.testapp.post_json(
32
             '/api/v2/sessions/login',
57
             '/api/v2/sessions/login',
33
             status=400,
58
             status=400,
34
             params=params,
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
         params = {
67
         params = {
39
             'email': 'unknown_user@unknown.unknown',
68
             'email': 'unknown_user@unknown.unknown',
40
             'password': 'bad_password',
69
             'password': 'bad_password',
41
         }
70
         }
42
-        res = self.testapp.get(
71
+        res = self.testapp.post_json(
43
             '/api/v2/sessions/login',
72
             '/api/v2/sessions/login',
44
             status=400,
73
             status=400,
45
             params=params,
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
 class TestWhoamiEndpoint(FunctionalTest):
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
         self.testapp.authorization = (
92
         self.testapp.authorization = (
56
             'Basic',
93
             'Basic',
57
             (
94
             (
70
         assert res.json_body['caldav_url'] is None
107
         assert res.json_body['caldav_url'] is None
71
         assert res.json_body['avatar_url'] is None
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
         self.testapp.authorization = (
111
         self.testapp.authorization = (
75
             'Basic',
112
             'Basic',
76
             (
113
             (
79
             )
116
             )
80
         )
117
         )
81
         res = self.testapp.get('/api/v2/sessions/whoami', status=401)
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
 # coding=utf-8
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
 from sqlalchemy.orm.exc import NoResultFound
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
 from tracim import TracimRequest
10
 from tracim import TracimRequest
10
 from tracim.extensions import hapic
11
 from tracim.extensions import hapic
11
 from tracim.lib.core.user import UserApi
12
 from tracim.lib.core.user import UserApi
12
 from tracim.models.context_models import UserInContext
13
 from tracim.models.context_models import UserInContext
13
 from tracim.views.controllers import Controller
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
 from tracim.views.core_api.schemas import LoginOutputHeaders
17
 from tracim.views.core_api.schemas import LoginOutputHeaders
19
 from tracim.views.core_api.schemas import BasicAuthSchema
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
 class SessionController(Controller):
23
 class SessionController(Controller):
29
 
24
 
30
     @hapic.with_api_doc()
25
     @hapic.with_api_doc()
31
     @hapic.input_headers(LoginOutputHeaders())
26
     @hapic.input_headers(LoginOutputHeaders())
32
-    @hapic.input_query(BasicAuthSchema())
27
+    @hapic.input_body(BasicAuthSchema())
33
     @hapic.handle_exception(LoginFailed, http_code=HTTPStatus.BAD_REQUEST)
28
     @hapic.handle_exception(LoginFailed, http_code=HTTPStatus.BAD_REQUEST)
34
     # TODO - G.M - 17-04-2018 - fix output header ?
29
     # TODO - G.M - 17-04-2018 - fix output header ?
35
     # @hapic.output_headers()
30
     # @hapic.output_headers()
41
         """
36
         """
42
         Logs user into the system
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
         app_config = request.registry.settings['CFG']
41
         app_config = request.registry.settings['CFG']
49
         try:
42
         try:
50
             uapi = UserApi(
43
             uapi = UserApi(
96
     def bind(self, configurator: Configurator):
89
     def bind(self, configurator: Configurator):
97
 
90
 
98
         # Login
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
         # Logout
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
         # Whoami
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
-# 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

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