~homeworkprod/byceps

22abce0821c38ca59f6f34f91a70999d4e00eae1 — Jochen Kupperschmidt 1 year, 4 months ago 5785afc
Introduce user event for user detail changes
M byceps/blueprints/user/current/views.py => byceps/blueprints/user/current/views.py +1 -0
@@ 123,6 123,7 @@ def details_update():
        city,
        street,
        phone_number,
        current_user.id,  # initiator_id
    )

    flash_success('Deine Daten wurden gespeichert.')

M byceps/events/user.py => byceps/events/user.py +5 -0
@@ 41,6 41,11 @@ class UserAccountUnsuspended(_UserEvent):


@dataclass(frozen=True)
class UserDetailsUpdated(_UserEvent):
    pass


@dataclass(frozen=True)
class UserEmailAddressChanged(_UserEvent):
    pass


M byceps/services/user/command_service.py => byceps/services/user/command_service.py +52 -2
@@ 7,13 7,14 @@ byceps.services.user.command_service
"""

from datetime import date
from typing import Optional
from typing import Any, Optional

from ...database import db
from ...events.user import (
    UserAccountDeleted,
    UserAccountSuspended,
    UserAccountUnsuspended,
    UserDetailsUpdated,
    UserEmailAddressChanged,
    UserScreenNameChanged,
)


@@ 24,6 25,7 @@ from ..authorization import service as authorization_service

from . import event_service
from .models.detail import UserDetail as DbUserDetail
from .models.event import UserEventData
from .models.user import User as DbUser
from . import service as user_service



@@ 227,10 229,20 @@ def update_user_details(
    city: str,
    street: str,
    phone_number: str,
) -> None:
    initiator_id: UserID,
) -> UserDetailsUpdated:
    """Update the user's details."""
    detail = _get_user_detail(user_id)

    old_first_names = detail.first_names
    old_last_name = detail.last_name
    old_date_of_birth = detail.date_of_birth
    old_country = detail.country
    old_zip_code = detail.zip_code
    old_city = detail.city
    old_street = detail.street
    old_phone_number = detail.phone_number

    detail.first_names = first_names
    detail.last_name = last_name
    detail.date_of_birth = date_of_birth


@@ 240,8 252,46 @@ def update_user_details(
    detail.street = street
    detail.phone_number = phone_number

    event_data = {
        'initiator_id': str(initiator_id),
    }
    _add_if_different(event_data, 'first_names', old_first_names, first_names)
    _add_if_different(event_data, 'last_name', old_last_name, last_name)
    _add_if_different(
        event_data, 'date_of_birth', old_date_of_birth, date_of_birth
    )
    _add_if_different(event_data, 'country', old_country, country)
    _add_if_different(event_data, 'zip_code', old_zip_code, zip_code)
    _add_if_different(event_data, 'city', old_city, city)
    _add_if_different(event_data, 'street', old_street, street)
    _add_if_different(
        event_data, 'phone_number', old_phone_number, phone_number
    )
    event = event_service.build_event(
        'user-details-updated', user_id, event_data
    )
    db.session.add(event)

    db.session.commit()

    return UserDetailsUpdated(
        occurred_at=event.occurred_at,
        user_id=event.user_id,
        initiator_id=initiator_id,
    )


def _add_if_different(
    event_data: UserEventData, base_key_name: str, old_value: str, new_value
) -> None:
    if old_value != new_value:
        event_data[f'old_{base_key_name}'] = _to_str_if_not_none(old_value)
        event_data[f'new_{base_key_name}'] = _to_str_if_not_none(new_value)


def _to_str_if_not_none(value: Any) -> Optional[str]:
    return str(value) if (value is not None) else None


def set_user_detail_extra(user_id: UserID, key: str, value: str) -> None:
    """Set a value for a key in the user's detail extras map."""

M tests/conftest.py => tests/conftest.py +16 -0
@@ 22,6 22,7 @@ from tests.helpers import (
    create_party,
    create_site,
    create_user,
    create_user_with_detail,
    DEFAULT_EMAIL_CONFIG_ID,
)



@@ 99,6 100,21 @@ def make_user(admin_app):


@pytest.fixture(scope='module')
def make_user_with_detail(admin_app):
    user_ids = set()

    def _wrapper(*args, **kwargs):
        user = create_user_with_detail(*args, **kwargs)
        user_ids.add(user.id)
        return user

    yield _wrapper

    for user_id in user_ids:
        user_command_service.delete_account(user_id, user_id, 'clean up')


@pytest.fixture(scope='module')
def admin_user(make_user):
    return make_user('Admin')


A tests/integration/services/user/test_update_user_details.py => tests/integration/services/user/test_update_user_details.py +187 -0
@@ 0,0 1,187 @@
"""
:Copyright: 2006-2020 Jochen Kupperschmidt
:License: Modified BSD, see LICENSE for details.
"""

from datetime import date

from byceps.events.user import UserDetailsUpdated
from byceps.services.user import command_service as user_command_service
from byceps.services.user import event_service


def test_update_user_address(party_app, make_user_with_detail):
    old_first_names = 'Rainer'
    old_last_name = 'Zufall'
    old_date_of_birth = None
    old_country = 'Germany'
    old_zip_code = '22999'
    old_city = 'Büttenwarder'
    old_street = 'Dorfweg 23'
    old_phone_number = None

    new_first_names = 'Rainer'
    new_last_name = 'Zufall'
    new_date_of_birth = None
    new_country = 'Germany'
    new_zip_code = '20099'
    new_city = 'Hamburg'
    new_street = 'Kirchenallee 1'
    new_phone_number = None

    user = make_user_with_detail(
        'RandomUser123',
        first_names=old_first_names,
        last_name=old_last_name,
        date_of_birth=old_date_of_birth,
        country=old_country,
        zip_code=old_zip_code,
        city=old_city,
        street=old_street,
        phone_number=old_phone_number,
    )

    events_before = event_service.get_events_for_user(user.id)
    assert len(events_before) == 0

    # -------------------------------- #

    event = user_command_service.update_user_details(
        user.id,
        new_first_names,
        new_last_name,
        new_date_of_birth,
        new_country,
        new_zip_code,
        new_city,
        new_street,
        new_phone_number,
        user.id,
    )

    # -------------------------------- #

    assert isinstance(event, UserDetailsUpdated)
    assert event.user_id == user.id
    assert event.initiator_id == user.id

    user_after = user_command_service._get_user(user.id)
    assert user_after.detail.first_names == new_first_names
    assert user_after.detail.last_name == new_last_name
    assert user_after.detail.date_of_birth == new_date_of_birth
    assert user_after.detail.country == new_country
    assert user_after.detail.zip_code == new_zip_code
    assert user_after.detail.city == new_city
    assert user_after.detail.street == new_street
    assert user_after.detail.phone_number == new_phone_number

    events_after = event_service.get_events_for_user(user_after.id)
    assert len(events_after) == 1

    details_updated_event = events_after[0]
    assert details_updated_event.event_type == 'user-details-updated'
    assert details_updated_event.data == {
        'initiator_id': str(user.id),
        'old_zip_code': old_zip_code,
        'new_zip_code': new_zip_code,
        'old_city': old_city,
        'new_city': new_city,
        'old_street': old_street,
        'new_street': new_street,
    }


def test_update_user_real_name(party_app, make_user_with_detail):
    old_first_names = 'Rainer'
    old_last_name = 'Zufall'

    new_first_names = 'Ryan R.'
    new_last_name = 'Wahnsinn'

    user = make_user_with_detail(
        'Moep',
        first_names=old_first_names,
        last_name=old_last_name,
    )

    events_before = event_service.get_events_for_user(user.id)
    assert len(events_before) == 0

    # -------------------------------- #

    event = user_command_service.update_user_details(
        user.id,
        new_first_names,
        new_last_name,
        user.detail.date_of_birth,
        user.detail.country,
        user.detail.zip_code,
        user.detail.city,
        user.detail.street,
        user.detail.phone_number,
        user.id,
    )

    # -------------------------------- #

    user_after = user_command_service._get_user(user.id)
    assert user_after.detail.first_names == new_first_names
    assert user_after.detail.last_name == new_last_name

    events_after = event_service.get_events_for_user(user_after.id)
    assert len(events_after) == 1

    details_updated_event = events_after[0]
    assert details_updated_event.event_type == 'user-details-updated'
    assert details_updated_event.data == {
        'initiator_id': str(user.id),
        'old_first_names': old_first_names,
        'new_first_names': new_first_names,
        'old_last_name': old_last_name,
        'new_last_name': new_last_name,
    }


def test_remove_user_dob_and_phone_number(party_app, make_user_with_detail):
    old_date_of_birth = date(1991, 9, 17)
    old_phone_number = '555-fake-anyway'

    user = make_user_with_detail(
        'FakeyMcFake',
        date_of_birth=old_date_of_birth,
        phone_number=old_phone_number,
    )

    # -------------------------------- #

    event = user_command_service.update_user_details(
        user.id,
        user.detail.first_names,
        user.detail.last_name,
        None,
        user.detail.country,
        user.detail.zip_code,
        user.detail.city,
        user.detail.street,
        '',
        user.id,
    )

    # -------------------------------- #

    user_after = user_command_service._get_user(user.id)
    assert user_after.detail.date_of_birth is None
    assert user_after.detail.phone_number == ''

    events_after = event_service.get_events_for_user(user_after.id)
    assert len(events_after) == 1

    details_updated_event = events_after[0]
    assert details_updated_event.event_type == 'user-details-updated'
    assert details_updated_event.data == {
        'initiator_id': str(user.id),
        'old_date_of_birth': '1991-09-17',
        'new_date_of_birth': None,
        'old_phone_number': old_phone_number,
        'new_phone_number': '',
    }