From 8da5c2663307884c65e7d15e6cd1f32a658c48d5 Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 26 Sep 2024 14:24:24 +0200 Subject: [PATCH 1/5] splitted update_personal_data() into 2 APIs: update_user_name() and update_user_language() --- src/eduid/webapp/personal_data/schemas.py | 32 ++++++++++++ src/eduid/webapp/personal_data/views.py | 63 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/src/eduid/webapp/personal_data/schemas.py b/src/eduid/webapp/personal_data/schemas.py index 24721c1c5..102cf8efb 100644 --- a/src/eduid/webapp/personal_data/schemas.py +++ b/src/eduid/webapp/personal_data/schemas.py @@ -27,6 +27,25 @@ class PersonalDataSchema(EduidSchema): legal_name = fields.String(required=False) language = fields.String(required=True, attribute="preferredLanguage") +class UserNameRequestSchema(EduidSchema, CSRFRequestMixin): + given_name = fields.String(required=True, validate=[validate_nonempty]) + chosen_given_name = fields.String(required=False) + surname = fields.String(required=True, validate=[validate_nonempty]) + legal_name = fields.String(required=False) + +class UserNameSchema(EduidSchema): + given_name = fields.String(required=True, attribute="givenName") + chosen_given_name = fields.String(required=False) + surname = fields.String(required=True) + legal_name = fields.String(required=False) + language = fields.String(required=True, attribute="preferredLanguage") + + +class UserLanguageRequestSchema(EduidSchema, CSRFRequestMixin): + language = fields.String(required=True, default="en", validate=validate_language) + +class UserLanguageSchema(EduidSchema): + language = fields.String(required=True, attribute="preferredLanguage") class UserPreferencesSchema(EduidSchema): always_use_security_key = fields.Boolean(required=True, default=True) @@ -49,6 +68,19 @@ class PersonalDataResponsePayload(PersonalDataSchema, CSRFResponseMixin): payload = fields.Nested(PersonalDataResponsePayload) +class UserNameResponseSchema(FluxStandardAction): + class UserNameResponsePayload(UserNameSchema, CSRFResponseMixin): + pass + + payload = fields.Nested(UserNameResponsePayload) + + +class UserLanguageResponseSchema(FluxStandardAction): + class UserLanguageResponsePayload(UserLanguageSchema, CSRFResponseMixin): + pass + + payload = fields.Nested(UserLanguageResponsePayload) + class IdentitiesResponseSchema(FluxStandardAction): class IdentitiesResponsePayload(EmailSchema, CSRFResponseMixin): diff --git a/src/eduid/webapp/personal_data/views.py b/src/eduid/webapp/personal_data/views.py index e45d5f961..d39f8b0f0 100644 --- a/src/eduid/webapp/personal_data/views.py +++ b/src/eduid/webapp/personal_data/views.py @@ -14,6 +14,10 @@ IdentitiesResponseSchema, PersonalDataRequestSchema, PersonalDataResponseSchema, + UserLanguageRequestSchema, + UserLanguageResponseSchema, + UserNameRequestSchema, + UserNameResponseSchema, UserPreferencesRequestSchema, UserPreferencesResponseSchema, ) @@ -82,6 +86,65 @@ def update_personal_data( personal_data = personal_data_user.to_dict() return success_response(payload=personal_data, message=PDataMsg.save_success) +@pd_views.route("/user/name", methods=["POST"]) +@UnmarshalWith(UserNameRequestSchema) +@MarshalWith(UserNameResponseSchema) +@require_user +def update_user_name( + user: User, given_name: str, surname: str, chosen_given_name: str | None = None +) -> FluxData: + personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) + current_app.logger.debug(f"Trying to save user {user}") + + # disallow change of first name, surname if the user is verified + if not user.identities.is_verified: + personal_data_user.given_name = given_name + personal_data_user.surname = surname + + # set chosen given name to either given name or a subset of given name if supplied + # also allow to set chosen given name to None + if ( + chosen_given_name is not None + and is_valid_chosen_given_name(personal_data_user.given_name, chosen_given_name) is False + ): + return error_response(message=PDataMsg.chosen_given_name_invalid) + + # mypy borked? + # error: Incompatible types in assignment (expression has type "str | None", variable has type "str") + personal_data_user.chosen_given_name = chosen_given_name + + try: + save_and_sync_user(personal_data_user) + except UserOutOfSync: + return error_response(message=CommonMsg.out_of_sync) + current_app.stats.count(name="personal_data_saved", value=1) + current_app.logger.info(f"Saved personal data for user {personal_data_user}") + + personal_data = personal_data_user.to_dict() + return success_response(payload=personal_data, message=PDataMsg.save_success) + +@pd_views.route("/use/language", methods=["POST"]) +@UnmarshalWith(UserLanguageRequestSchema) +@MarshalWith(UserLanguageResponseSchema) +@require_user +def update_user_language( + user: User, language: str +) -> FluxData: + personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) + current_app.logger.debug(f"Trying to save user {user}") + + personal_data_user.language = language + + try: + save_and_sync_user(personal_data_user) + except UserOutOfSync: + return error_response(message=CommonMsg.out_of_sync) + current_app.stats.count(name="personal_data_saved", value=1) + current_app.logger.info(f"Saved personal data for user {personal_data_user}") + + personal_data = personal_data_user.to_dict() + return success_response(payload=personal_data, message=PDataMsg.save_success) + @pd_views.route("/preferences", methods=["GET"]) @MarshalWith(UserPreferencesResponseSchema) From 12d274aa27173fb6a9ea9f688da2655f15726c4a Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 26 Sep 2024 14:47:00 +0200 Subject: [PATCH 2/5] fix url name for language --- src/eduid/webapp/personal_data/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eduid/webapp/personal_data/views.py b/src/eduid/webapp/personal_data/views.py index d39f8b0f0..c6c8b2b58 100644 --- a/src/eduid/webapp/personal_data/views.py +++ b/src/eduid/webapp/personal_data/views.py @@ -123,7 +123,7 @@ def update_user_name( personal_data = personal_data_user.to_dict() return success_response(payload=personal_data, message=PDataMsg.save_success) -@pd_views.route("/use/language", methods=["POST"]) +@pd_views.route("/user/language", methods=["POST"]) @UnmarshalWith(UserLanguageRequestSchema) @MarshalWith(UserLanguageResponseSchema) @require_user From ac32759f1186d586cd4bb903a1ad72c47d7b794e Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 26 Sep 2024 15:20:57 +0200 Subject: [PATCH 3/5] updated current_app.stats.count to correct name, added depreacated decorator to update_personal_data(), removed language from UserNameSchema --- src/eduid/webapp/personal_data/schemas.py | 1 - src/eduid/webapp/personal_data/views.py | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/eduid/webapp/personal_data/schemas.py b/src/eduid/webapp/personal_data/schemas.py index 102cf8efb..88d8192cd 100644 --- a/src/eduid/webapp/personal_data/schemas.py +++ b/src/eduid/webapp/personal_data/schemas.py @@ -38,7 +38,6 @@ class UserNameSchema(EduidSchema): chosen_given_name = fields.String(required=False) surname = fields.String(required=True) legal_name = fields.String(required=False) - language = fields.String(required=True, attribute="preferredLanguage") class UserLanguageRequestSchema(EduidSchema, CSRFRequestMixin): diff --git a/src/eduid/webapp/personal_data/views.py b/src/eduid/webapp/personal_data/views.py index c6c8b2b58..6869a2992 100644 --- a/src/eduid/webapp/personal_data/views.py +++ b/src/eduid/webapp/personal_data/views.py @@ -1,3 +1,4 @@ +from eduid.common.decorators import deprecated from flask import Blueprint from eduid.common.config.base import FrontendAction @@ -48,6 +49,7 @@ def get_user(user: User) -> FluxData: return success_response(payload=user.to_dict()) +@deprecated("update_personal_data view is deprecated, use update_user_name or update_user_language view instead") @pd_views.route("/user", methods=["POST"]) @UnmarshalWith(PersonalDataRequestSchema) @MarshalWith(PersonalDataResponseSchema) @@ -117,7 +119,7 @@ def update_user_name( save_and_sync_user(personal_data_user) except UserOutOfSync: return error_response(message=CommonMsg.out_of_sync) - current_app.stats.count(name="personal_data_saved", value=1) + current_app.stats.count(name="user_name_saved", value=1) current_app.logger.info(f"Saved personal data for user {personal_data_user}") personal_data = personal_data_user.to_dict() @@ -139,7 +141,7 @@ def update_user_language( save_and_sync_user(personal_data_user) except UserOutOfSync: return error_response(message=CommonMsg.out_of_sync) - current_app.stats.count(name="personal_data_saved", value=1) + current_app.stats.count(name="user_language_saved", value=1) current_app.logger.info(f"Saved personal data for user {personal_data_user}") personal_data = personal_data_user.to_dict() From 7a75beedbf7014fb46ba002182faccc71174c37c Mon Sep 17 00:00:00 2001 From: Alessandro Date: Thu, 26 Sep 2024 17:17:36 +0200 Subject: [PATCH 4/5] added tests for update_user_name()and update_user_language() --- .../webapp/personal_data/tests/test_app.py | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/src/eduid/webapp/personal_data/tests/test_app.py b/src/eduid/webapp/personal_data/tests/test_app.py index 25aefca65..7de35477c 100644 --- a/src/eduid/webapp/personal_data/tests/test_app.py +++ b/src/eduid/webapp/personal_data/tests/test_app.py @@ -93,6 +93,63 @@ def _post_user( data.update(mod_data) return client.post("/user", data=json.dumps(data), content_type=self.content_type_json) + @patch("eduid.common.rpc.am_relay.AmRelay.request_user_sync") + def _post_user_name( + self, mock_request_user_sync: Any, mod_data: dict[str, Any] | None = None, verified_user: bool = True + ): + """ + POST user name for the test user + """ + mock_request_user_sync.side_effect = self.request_user_sync + eppn = self.test_user_data["eduPersonPrincipalName"] + + if not verified_user: + # Remove verified identities from the users + user = self.app.central_userdb.get_user_by_eppn(eppn) + for identity in user.identities.verified: + user.identities.remove(ElementKey(identity.identity_type.value)) + self.app.central_userdb.save(user) + + with self.session_cookie(self.browser, eppn) as client: + with self.app.test_request_context(): + with client.session_transaction() as sess: + data = { + "given_name": "Peter", + "surname": "Johnson", + "csrf_token": sess.get_csrf_token(), + } + if mod_data: + data.update(mod_data) + return client.post("/user/name", data=json.dumps(data), content_type=self.content_type_json) + + @patch("eduid.common.rpc.am_relay.AmRelay.request_user_sync") + def _post_user_language( + self, mock_request_user_sync: Any, mod_data: dict[str, Any] | None = None, verified_user: bool = True + ): + """ + POST user language for the test user + """ + mock_request_user_sync.side_effect = self.request_user_sync + eppn = self.test_user_data["eduPersonPrincipalName"] + + if not verified_user: + # Remove verified identities from the users + user = self.app.central_userdb.get_user_by_eppn(eppn) + for identity in user.identities.verified: + user.identities.remove(ElementKey(identity.identity_type.value)) + self.app.central_userdb.save(user) + + with self.session_cookie(self.browser, eppn) as client: + with self.app.test_request_context(): + with client.session_transaction() as sess: + data = { + "language": "en", + "csrf_token": sess.get_csrf_token(), + } + if mod_data: + data.update(mod_data) + return client.post("/user/language", data=json.dumps(data), content_type=self.content_type_json) + def _get_preferences(self, eppn: str | None = None): """ Send a GET request to get the personal data of a user @@ -207,6 +264,21 @@ def test_post_user(self): } self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_name(self): + response = self._post_user_name(verified_user=False) + expected_payload = { + "surname": "Johnson", + "given_name": "Peter", + } + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + + def test_post_user_language(self): + response = self._post_user_language(verified_user=False) + expected_payload = { + "language": "en", + } + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_set_chosen_given_name_and_language_verified_user(self): expected_payload = { "surname": "Smith", @@ -216,6 +288,22 @@ def test_set_chosen_given_name_and_language_verified_user(self): response = self._post_user(mod_data=expected_payload) self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_name_set_chosen_given_name_verified_user(self): + expected_payload = { + "surname": "Smith", + "given_name": "John", + } + response = self._post_user_name(mod_data=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + + def test_post_user_language_set_language_verified_user(self): + expected_payload = { + "language": "sv", + } + response = self._post_user_language(mod_data=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + + def test_set_given_name_and_surname_verified_user(self): mod_data = { "surname": "Johnson", @@ -230,31 +318,68 @@ def test_set_given_name_and_surname_verified_user(self): response = self._post_user(mod_data=mod_data) self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_name_set_given_name_and_surname_verified_user(self): + mod_data = { + "surname": "Johnson", + "given_name": "Peter", + } + expected_payload = { + "surname": "Smith", + "given_name": "John", + } + response = self._post_user_name(mod_data=mod_data) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_bad_csrf(self): response = self._post_user(mod_data={"csrf_token": "wrong-token"}) expected_payload = {"error": {"csrf_token": ["CSRF failed to validate"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user__name_bad_csrf(self): + response = self._post_user_name(mod_data={"csrf_token": "wrong-token"}) + expected_payload = {"error": {"csrf_token": ["CSRF failed to validate"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_no_given_name(self): response = self._post_user(mod_data={"given_name": ""}) expected_payload = {"error": {"given_name": ["pdata.field_required"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_name_no_given_name(self): + response = self._post_user_name(mod_data={"given_name": ""}) + expected_payload = {"error": {"given_name": ["pdata.field_required"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_blank_given_name(self): response = self._post_user(mod_data={"given_name": " "}) expected_payload = {"error": {"given_name": ["pdata.field_required"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_name_blank_given_name(self): + response = self._post_user_name(mod_data={"given_name": " "}) + expected_payload = {"error": {"given_name": ["pdata.field_required"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_no_surname(self): response = self._post_user(mod_data={"surname": ""}) expected_payload = {"error": {"surname": ["pdata.field_required"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_name_no_surname(self): + response = self._post_user_name(mod_data={"surname": ""}) + expected_payload = {"error": {"surname": ["pdata.field_required"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_blank_surname(self): response = self._post_user(mod_data={"surname": " "}) expected_payload = {"error": {"surname": ["pdata.field_required"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_name_blank_surname(self): + response = self._post_user_name(mod_data={"surname": " "}) + expected_payload = {"error": {"surname": ["pdata.field_required"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_with_chosen_given_name(self): response = self._post_user(mod_data={"chosen_given_name": "Peter"}, verified_user=False) expected_payload = { @@ -265,12 +390,27 @@ def test_post_user_with_chosen_given_name(self): } self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_name_with_chosen_given_name(self): + response = self._post_user_name(mod_data={"chosen_given_name": "Peter"}, verified_user=False) + expected_payload = { + "surname": "Johnson", + "given_name": "Peter", + "chosen_given_name": "Peter", + } + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_with_bad_chosen_given_name(self): response = self._post_user(mod_data={"chosen_given_name": "Michael"}, verified_user=False) self._check_error_response( response, type_="POST_PERSONAL_DATA_USER_FAIL", msg=PDataMsg.chosen_given_name_invalid ) + def test_post_user_name_with_bad_chosen_given_name(self): + response = self._post_user_name(mod_data={"chosen_given_name": "Michael"}, verified_user=False) + self._check_error_response( + response, type_="POST_PERSONAL_DATA_USER_FAIL", msg=PDataMsg.chosen_given_name_invalid + ) + def test_post_user_to_unset_chosen_given_name(self): # set test user chosen given name self.test_user.chosen_given_name = "Peter" @@ -286,16 +426,40 @@ def test_post_user_to_unset_chosen_given_name(self): } self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_name_to_unset_chosen_given_name(self): + # set test user chosen given name + self.test_user.chosen_given_name = "Peter" + self.app.central_userdb.save(self.test_user) + user = self.app.central_userdb.get_user_by_eppn(eppn=self.test_user.eppn) + assert user.chosen_given_name == "Peter" + + response = self._post_user_name(verified_user=False) + expected_payload = { + "surname": "Johnson", + "given_name": "Peter", + } + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + def test_post_user_no_language(self): response = self._post_user(mod_data={"language": ""}) expected_payload = {"error": {"language": ["Language '' is not available"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_language_no_language(self): + response = self._post_user_language(mod_data={"language": ""}) + expected_payload = {"error": {"language": ["Language '' is not available"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_unknown_language(self): response = self._post_user(mod_data={"language": "es"}) expected_payload = {"error": {"language": ["Language 'es' is not available"]}} self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_post_user_language_unknown_language(self): + response = self._post_user_language(mod_data={"language": "es"}) + expected_payload = {"error": {"language": ["Language 'es' is not available"]}} + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + def test_get_preferences(self): response = self._get_preferences() expected_payload = {"always_use_security_key": True} From 17f767a9f0a59f20242fc3dffd2ee6236afb347e Mon Sep 17 00:00:00 2001 From: Alessandro Date: Fri, 27 Sep 2024 10:40:19 +0200 Subject: [PATCH 5/5] fixed tests for user_name() and user_language() --- src/eduid/webapp/personal_data/schemas.py | 5 +++ .../webapp/personal_data/tests/test_app.py | 42 +++++++++---------- src/eduid/webapp/personal_data/views.py | 14 +++---- 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/eduid/webapp/personal_data/schemas.py b/src/eduid/webapp/personal_data/schemas.py index 88d8192cd..5443aa708 100644 --- a/src/eduid/webapp/personal_data/schemas.py +++ b/src/eduid/webapp/personal_data/schemas.py @@ -27,12 +27,14 @@ class PersonalDataSchema(EduidSchema): legal_name = fields.String(required=False) language = fields.String(required=True, attribute="preferredLanguage") + class UserNameRequestSchema(EduidSchema, CSRFRequestMixin): given_name = fields.String(required=True, validate=[validate_nonempty]) chosen_given_name = fields.String(required=False) surname = fields.String(required=True, validate=[validate_nonempty]) legal_name = fields.String(required=False) + class UserNameSchema(EduidSchema): given_name = fields.String(required=True, attribute="givenName") chosen_given_name = fields.String(required=False) @@ -43,9 +45,11 @@ class UserNameSchema(EduidSchema): class UserLanguageRequestSchema(EduidSchema, CSRFRequestMixin): language = fields.String(required=True, default="en", validate=validate_language) + class UserLanguageSchema(EduidSchema): language = fields.String(required=True, attribute="preferredLanguage") + class UserPreferencesSchema(EduidSchema): always_use_security_key = fields.Boolean(required=True, default=True) @@ -67,6 +71,7 @@ class PersonalDataResponsePayload(PersonalDataSchema, CSRFResponseMixin): payload = fields.Nested(PersonalDataResponsePayload) + class UserNameResponseSchema(FluxStandardAction): class UserNameResponsePayload(UserNameSchema, CSRFResponseMixin): pass diff --git a/src/eduid/webapp/personal_data/tests/test_app.py b/src/eduid/webapp/personal_data/tests/test_app.py index 7de35477c..dffed9eb8 100644 --- a/src/eduid/webapp/personal_data/tests/test_app.py +++ b/src/eduid/webapp/personal_data/tests/test_app.py @@ -132,13 +132,6 @@ def _post_user_language( mock_request_user_sync.side_effect = self.request_user_sync eppn = self.test_user_data["eduPersonPrincipalName"] - if not verified_user: - # Remove verified identities from the users - user = self.app.central_userdb.get_user_by_eppn(eppn) - for identity in user.identities.verified: - user.identities.remove(ElementKey(identity.identity_type.value)) - self.app.central_userdb.save(user) - with self.session_cookie(self.browser, eppn) as client: with self.app.test_request_context(): with client.session_transaction() as sess: @@ -270,14 +263,16 @@ def test_post_user_name(self): "surname": "Johnson", "given_name": "Peter", } - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_NAME_SUCCESS", payload=expected_payload) def test_post_user_language(self): response = self._post_user_language(verified_user=False) expected_payload = { "language": "en", } - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response( + response, type_="POST_PERSONAL_DATA_USER_LANGUAGE_SUCCESS", payload=expected_payload + ) def test_set_chosen_given_name_and_language_verified_user(self): expected_payload = { @@ -294,15 +289,16 @@ def test_post_user_name_set_chosen_given_name_verified_user(self): "given_name": "John", } response = self._post_user_name(mod_data=expected_payload) - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_NAME_SUCCESS", payload=expected_payload) def test_post_user_language_set_language_verified_user(self): expected_payload = { "language": "sv", } response = self._post_user_language(mod_data=expected_payload) - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) - + self._check_success_response( + response, type_="POST_PERSONAL_DATA_USER_LANGUAGE_SUCCESS", payload=expected_payload + ) def test_set_given_name_and_surname_verified_user(self): mod_data = { @@ -328,7 +324,7 @@ def test_post_user_name_set_given_name_and_surname_verified_user(self): "given_name": "John", } response = self._post_user_name(mod_data=mod_data) - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_NAME_SUCCESS", payload=expected_payload) def test_post_user_bad_csrf(self): response = self._post_user(mod_data={"csrf_token": "wrong-token"}) @@ -338,7 +334,7 @@ def test_post_user_bad_csrf(self): def test_post_user__name_bad_csrf(self): response = self._post_user_name(mod_data={"csrf_token": "wrong-token"}) expected_payload = {"error": {"csrf_token": ["CSRF failed to validate"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", payload=expected_payload) def test_post_user_no_given_name(self): response = self._post_user(mod_data={"given_name": ""}) @@ -348,7 +344,7 @@ def test_post_user_no_given_name(self): def test_post_user_name_no_given_name(self): response = self._post_user_name(mod_data={"given_name": ""}) expected_payload = {"error": {"given_name": ["pdata.field_required"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", payload=expected_payload) def test_post_user_blank_given_name(self): response = self._post_user(mod_data={"given_name": " "}) @@ -358,7 +354,7 @@ def test_post_user_blank_given_name(self): def test_post_user_name_blank_given_name(self): response = self._post_user_name(mod_data={"given_name": " "}) expected_payload = {"error": {"given_name": ["pdata.field_required"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", payload=expected_payload) def test_post_user_no_surname(self): response = self._post_user(mod_data={"surname": ""}) @@ -368,7 +364,7 @@ def test_post_user_no_surname(self): def test_post_user_name_no_surname(self): response = self._post_user_name(mod_data={"surname": ""}) expected_payload = {"error": {"surname": ["pdata.field_required"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", payload=expected_payload) def test_post_user_blank_surname(self): response = self._post_user(mod_data={"surname": " "}) @@ -378,7 +374,7 @@ def test_post_user_blank_surname(self): def test_post_user_name_blank_surname(self): response = self._post_user_name(mod_data={"surname": " "}) expected_payload = {"error": {"surname": ["pdata.field_required"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", payload=expected_payload) def test_post_user_with_chosen_given_name(self): response = self._post_user(mod_data={"chosen_given_name": "Peter"}, verified_user=False) @@ -397,7 +393,7 @@ def test_post_user_name_with_chosen_given_name(self): "given_name": "Peter", "chosen_given_name": "Peter", } - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_NAME_SUCCESS", payload=expected_payload) def test_post_user_with_bad_chosen_given_name(self): response = self._post_user(mod_data={"chosen_given_name": "Michael"}, verified_user=False) @@ -408,7 +404,7 @@ def test_post_user_with_bad_chosen_given_name(self): def test_post_user_name_with_bad_chosen_given_name(self): response = self._post_user_name(mod_data={"chosen_given_name": "Michael"}, verified_user=False) self._check_error_response( - response, type_="POST_PERSONAL_DATA_USER_FAIL", msg=PDataMsg.chosen_given_name_invalid + response, type_="POST_PERSONAL_DATA_USER_NAME_FAIL", msg=PDataMsg.chosen_given_name_invalid ) def test_post_user_to_unset_chosen_given_name(self): @@ -438,7 +434,7 @@ def test_post_user_name_to_unset_chosen_given_name(self): "surname": "Johnson", "given_name": "Peter", } - self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_SUCCESS", payload=expected_payload) + self._check_success_response(response, type_="POST_PERSONAL_DATA_USER_NAME_SUCCESS", payload=expected_payload) def test_post_user_no_language(self): response = self._post_user(mod_data={"language": ""}) @@ -448,7 +444,7 @@ def test_post_user_no_language(self): def test_post_user_language_no_language(self): response = self._post_user_language(mod_data={"language": ""}) expected_payload = {"error": {"language": ["Language '' is not available"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_LANGUAGE_FAIL", payload=expected_payload) def test_post_user_unknown_language(self): response = self._post_user(mod_data={"language": "es"}) @@ -458,7 +454,7 @@ def test_post_user_unknown_language(self): def test_post_user_language_unknown_language(self): response = self._post_user_language(mod_data={"language": "es"}) expected_payload = {"error": {"language": ["Language 'es' is not available"]}} - self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_FAIL", payload=expected_payload) + self._check_error_response(response, type_="POST_PERSONAL_DATA_USER_LANGUAGE_FAIL", payload=expected_payload) def test_get_preferences(self): response = self._get_preferences() diff --git a/src/eduid/webapp/personal_data/views.py b/src/eduid/webapp/personal_data/views.py index 6869a2992..96d46716b 100644 --- a/src/eduid/webapp/personal_data/views.py +++ b/src/eduid/webapp/personal_data/views.py @@ -1,7 +1,7 @@ -from eduid.common.decorators import deprecated from flask import Blueprint from eduid.common.config.base import FrontendAction +from eduid.common.decorators import deprecated from eduid.userdb import User from eduid.userdb.exceptions import UserOutOfSync from eduid.userdb.personal_data import PersonalDataUser @@ -49,7 +49,7 @@ def get_user(user: User) -> FluxData: return success_response(payload=user.to_dict()) -@deprecated("update_personal_data view is deprecated, use update_user_name or update_user_language view instead") +@deprecated("update_personal_data view is deprecated, use update_user_name or update_user_language view instead") @pd_views.route("/user", methods=["POST"]) @UnmarshalWith(PersonalDataRequestSchema) @MarshalWith(PersonalDataResponseSchema) @@ -88,13 +88,12 @@ def update_personal_data( personal_data = personal_data_user.to_dict() return success_response(payload=personal_data, message=PDataMsg.save_success) + @pd_views.route("/user/name", methods=["POST"]) @UnmarshalWith(UserNameRequestSchema) @MarshalWith(UserNameResponseSchema) @require_user -def update_user_name( - user: User, given_name: str, surname: str, chosen_given_name: str | None = None -) -> FluxData: +def update_user_name(user: User, given_name: str, surname: str, chosen_given_name: str | None = None) -> FluxData: personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) current_app.logger.debug(f"Trying to save user {user}") @@ -125,13 +124,12 @@ def update_user_name( personal_data = personal_data_user.to_dict() return success_response(payload=personal_data, message=PDataMsg.save_success) + @pd_views.route("/user/language", methods=["POST"]) @UnmarshalWith(UserLanguageRequestSchema) @MarshalWith(UserLanguageResponseSchema) @require_user -def update_user_language( - user: User, language: str -) -> FluxData: +def update_user_language(user: User, language: str) -> FluxData: personal_data_user = PersonalDataUser.from_user(user, current_app.private_userdb) current_app.logger.debug(f"Trying to save user {user}")