diff --git a/src/open_inwoner/accounts/tests/test_profile_views.py b/src/open_inwoner/accounts/tests/test_profile_views.py index c13f662501..998fa031e6 100644 --- a/src/open_inwoner/accounts/tests/test_profile_views.py +++ b/src/open_inwoner/accounts/tests/test_profile_views.py @@ -1,12 +1,9 @@ -import io - from django.test import override_settings from django.urls import reverse from django.utils.translation import ugettext_lazy as _ import requests_mock from django_webtest import WebTest -from PIL import Image from timeline_logger.models import TimelineLog from webtest import Upload @@ -15,14 +12,15 @@ from open_inwoner.haalcentraal.tests.mixins import HaalCentraalMixin from open_inwoner.pdc.tests.factories import CategoryFactory from open_inwoner.utils.logentry import LOG_ACTIONS -from open_inwoner.utils.tests.helpers import create_image_bytes +from open_inwoner.utils.tests.helpers import AssertTimelineLogMixin, create_image_bytes from ...cms.profile.cms_apps import ProfileApphook from ...cms.tests import cms_tools +from ...openklant.tests.data import MockAPIReadPatchData from ...questionnaire.tests.factories import QuestionnaireStepFactory from ..choices import ContactTypeChoices, LoginTypeChoices from ..forms import BrpUserForm, UserForm -from .factories import ActionFactory, DocumentFactory, UserFactory +from .factories import ActionFactory, DigidUserFactory, DocumentFactory, UserFactory @override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") @@ -193,7 +191,7 @@ def test_expected_message_is_shown_when_all_notifications_disabled(self): @override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") -class EditProfileTests(WebTest): +class EditProfileTests(AssertTimelineLogMixin, WebTest): def setUp(self): self.url = reverse("profile:edit") self.return_url = reverse("profile:detail") @@ -435,6 +433,97 @@ def test_image_field_is_not_rendered_when_begeleider_and_digid_login(self): self.assertNotIn("image", form.fields.keys()) self.assertEqual(response.pyquery("#id_image"), []) + @requests_mock.Mocker() + def test_modify_phone_and_email_updates_klant_api(self, m): + MockAPIReadPatchData.setUpServices() + data = MockAPIReadPatchData().install_mocks(m) + + response = self.app.get(self.url, user=data.user) + form = response.forms["profile-edit"] + form["email"] = "new@example.com" + form["phonenumber"] = "01234456789" + form.submit() + + # user data tested in other cases + + self.assertTrue(data.matchers[0].called) + klant_patch_data = data.matchers[1].request_history[0].json() + self.assertEqual( + klant_patch_data, + { + "emailadres": "new@example.com", + "telefoonnummer": "01234456789", + }, + ) + self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog( + "patched klant from user profile edit with fields: emailadres, telefoonnummer" + ) + + @requests_mock.Mocker() + def test_modify_phone_updates_klant_api_but_skips_unchanged(self, m): + MockAPIReadPatchData.setUpServices() + data = MockAPIReadPatchData().install_mocks(m) + + response = self.app.get(self.url, user=data.user) + form = response.forms["profile-edit"] + form.submit() + + # user data tested in other cases + + self.assertFalse(data.matchers[0].called) + self.assertFalse(data.matchers[1].called) + + @requests_mock.Mocker() + def test_modify_phone_updates_klant_api_but_skip_unchanged_email(self, m): + MockAPIReadPatchData.setUpServices() + data = MockAPIReadPatchData().install_mocks(m) + + response = self.app.get(self.url, user=data.user) + form = response.forms["profile-edit"] + form["phonenumber"] = "01234456789" + form.submit() + + # user data tested in other cases + + self.assertTrue(data.matchers[0].called) + klant_patch_data = data.matchers[1].request_history[0].json() + self.assertEqual( + klant_patch_data, + { + "telefoonnummer": "01234456789", + }, + ) + self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog( + "patched klant from user profile edit with fields: telefoonnummer" + ) + + @requests_mock.Mocker() + def test_modify_phone_updates_klant_api_but_skip_unchanged_phone(self, m): + MockAPIReadPatchData.setUpServices() + data = MockAPIReadPatchData().install_mocks(m) + + response = self.app.get(self.url, user=data.user) + form = response.forms["profile-edit"] + form["email"] = "new@example.com" + form.submit() + + # user data tested in other cases + + self.assertTrue(data.matchers[0].called) + klant_patch_data = data.matchers[1].request_history[0].json() + self.assertEqual( + klant_patch_data, + { + "emailadres": "new@example.com", + }, + ) + self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog( + "patched klant from user profile edit with fields: emailadres" + ) + @requests_mock.Mocker() @override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") diff --git a/src/open_inwoner/accounts/views/profile.py b/src/open_inwoner/accounts/views/profile.py index 4403827160..15a38ac903 100644 --- a/src/open_inwoner/accounts/views/profile.py +++ b/src/open_inwoner/accounts/views/profile.py @@ -25,6 +25,7 @@ from open_inwoner.utils.mixins import ExportMixin from open_inwoner.utils.views import CommonPageMixin, LogMixin +from ...openklant.wrap import fetch_klant_for_bsn, patch_klant from ..forms import BrpUserForm, CategoriesForm, UserForm, UserNotificationsForm from ..models import Action, User @@ -133,10 +134,36 @@ def get_object(self): def form_valid(self, form): form.save() + self.update_klant_api({k: form.cleaned_data[k] for k in form.changed_data}) + messages.success(self.request, _("Uw wijzigingen zijn opgeslagen")) self.log_change(self.get_object(), _("profile was modified")) return HttpResponseRedirect(self.get_success_url()) + def update_klant_api(self, user_form_data: dict): + user: User = self.request.user + if not user.bsn or user.login_type != LoginTypeChoices.digid: + return + field_mapping = { + "emailadres": "email", + "telefoonnummer": "phonenumber", + } + update_data = { + api_name: user_form_data[local_name] + for api_name, local_name in field_mapping.items() + if user_form_data.get(local_name) + } + if update_data: + klant = fetch_klant_for_bsn(user.bsn) + if klant: + self.log_system_action("retrieved klant for BSN-user") + + klant = patch_klant(klant, update_data) + if klant: + self.log_system_action( + f"patched klant from user profile edit with fields: {', '.join(sorted(update_data.keys()))}" + ) + def get_form_class(self): user = self.request.user if user.is_digid_and_brp(): diff --git a/src/open_inwoner/openklant/tests/data.py b/src/open_inwoner/openklant/tests/data.py index 445159db11..8ab37c1b9c 100644 --- a/src/open_inwoner/openklant/tests/data.py +++ b/src/open_inwoner/openklant/tests/data.py @@ -28,6 +28,46 @@ def setUpOASMocks(self, m): mock_service_oas_get(m, CONTACTMOMENTEN_ROOT, "cmc") +class MockAPIReadPatchData(MockAPIData): + def __init__(self): + self.user = DigidUserFactory( + email="old@example.com", + phonenumber="0100000000", + ) + + self.klant_old = generate_oas_component( + "kc", + "schemas/Klant", + uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + url=f"{KLANTEN_ROOT}klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + emailadres="bad@example.com", + telefoonnummer="", + ) + self.klant_updated = generate_oas_component( + "kc", + "schemas/Klant", + uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + url=f"{KLANTEN_ROOT}klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + emailadres="good@example.com", + telefoonnummer="0123456789", + ) + + def install_mocks(self, m) -> "MockAPIReadPatchData": + self.setUpOASMocks(m) + self.matchers = [ + m.get( + f"{KLANTEN_ROOT}klanten?subjectNatuurlijkPersoon__inpBsn={self.user.bsn}", + json=paginated_response([self.klant_old]), + ), + m.patch( + f"{KLANTEN_ROOT}klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + json=self.klant_updated, + status_code=200, + ), + ] + return self + + class MockAPIReadData(MockAPIData): def __init__(self): self.user = DigidUserFactory(