Passed
Push — main ( 87df82...d49116 )
by Jochen
05:18
created

tests.helpers   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 257
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 176
dl 0
loc 257
rs 9.92
c 0
b 0
f 0
wmc 31

14 Functions

Rating   Name   Duplication   Size   Complexity  
A http_client() 0 13 2
A create_role_with_permissions_assigned() 0 7 2
A generate_token() 0 2 1
A create_admin_app() 0 6 1
A current_user_set() 0 7 2
A login_user() 0 3 1
A create_site() 0 30 3
A create_permissions() 0 4 2
C create_user() 0 68 8
A create_site_app() 0 4 1
A current_party_set() 0 8 2
A app_context() 0 8 2
A _add_user_credentials_to_session() 0 8 3
A create_party() 0 10 1
1
"""
2
tests.helpers
3
~~~~~~~~~~~~~
4
5
:Copyright: 2006-2021 Jochen Kupperschmidt
6
:License: Revised BSD (see `LICENSE` file for details)
7
"""
8
9
from __future__ import annotations
10
from contextlib import contextmanager
11
from datetime import date, datetime
12
from pathlib import Path
13
from secrets import token_hex
14
from typing import Any, Iterable, Optional, Union
15
16
from flask import appcontext_pushed, Flask, g
17
18
from byceps.application import create_app
19
from byceps.database import db, generate_uuid
20
from byceps.services.authentication.password import service as password_service
21
from byceps.services.authentication.session.models.current_user import (
22
    CurrentUser,
23
)
24
from byceps.services.authentication.session import service as session_service
25
from byceps.services.authorization import service as authz_service
26
from byceps.services.authorization.transfer.models import PermissionID, RoleID
27
from byceps.services.board.transfer.models import BoardID
28
from byceps.services.party import service as party_service
29
from byceps.services.party.transfer.models import Party
30
from byceps.services.shop.storefront.transfer.models import StorefrontID
31
from byceps.services.site import service as site_service
32
from byceps.services.site.transfer.models import SiteID
33
from byceps.services.user import creation_service as user_creation_service
34
from byceps.services.user.creation_service import UserCreationFailed
35
from byceps.services.user.dbmodels.detail import UserDetail as DbUserDetail
36
from byceps.services.user.dbmodels.user import User as DbUser
37
from byceps.typing import BrandID, PartyID, UserID
38
39
40
_CONFIG_PATH = Path('../config')
41
CONFIG_FILENAME_TEST_SITE = _CONFIG_PATH / 'test_site.py'
42
CONFIG_FILENAME_TEST_ADMIN = _CONFIG_PATH / 'test_admin.py'
43
44
45
def create_admin_app(
46
    config_overrides: Optional[dict[str, Any]] = None
47
) -> Flask:
48
    return create_app(
49
        config_filename=CONFIG_FILENAME_TEST_ADMIN,
50
        config_overrides=config_overrides,
51
    )
52
53
54
def create_site_app(config_overrides: Optional[dict[str, Any]] = None) -> Flask:
55
    return create_app(
56
        config_filename=CONFIG_FILENAME_TEST_SITE,
57
        config_overrides=config_overrides,
58
    )
59
60
61
def generate_token(n: int = 4) -> str:
62
    return token_hex(n)
63
64
65
@contextmanager
66
def app_context(
67
    *, config_filename: Optional[Union[Path, str]] = CONFIG_FILENAME_TEST_SITE
68
):
69
    app = create_app(config_filename=config_filename)
70
71
    with app.app_context():
72
        yield app
73
74
75
@contextmanager
76
def current_party_set(app: Flask, party: Party):
77
    def handler(sender, **kwargs):
78
        g.party_id = party.id
79
        g.brand_id = party.brand_id
80
81
    with appcontext_pushed.connected_to(handler, app):
82
        yield
83
84
85
@contextmanager
86
def current_user_set(app: Flask, current_user: CurrentUser):
87
    def handler(sender, **kwargs):
88
        g.user = current_user
89
90
    with appcontext_pushed.connected_to(handler, app):
91
        yield
92
93
94
DEFAULT_DATE_OF_BIRTH = date(1993, 2, 15)
95
96
97
def create_user(
98
    screen_name: Optional[str] = '__random__',
99
    *,
100
    user_id: Optional[UserID] = None,
101
    created_at: Optional[datetime] = None,
102
    email_address: Optional[str] = None,
103
    email_address_verified: bool = False,
104
    initialized: bool = True,
105
    suspended: bool = False,
106
    deleted: bool = False,
107
    legacy_id: Optional[int] = None,
108
    with_detail: bool = False,
109
    first_names: Optional[str] = 'John Joseph',
110
    last_name: Optional[str] = 'Doe',
111
    date_of_birth=DEFAULT_DATE_OF_BIRTH,
112
    country: Optional[str] = 'State of Mind',
113
    zip_code: Optional[str] = '31337',
114
    city: Optional[str] = 'Atrocity',
115
    street: Optional[str] = 'Elite Street 1337',
116
    phone_number: Optional[str] = '555-CALL-ME-MAYBE',
117
    password: Optional[str] = None,
118
) -> DbUser:
119
    if screen_name == '__random__':
120
        screen_name = generate_token(8)
121
122
    if not user_id:
123
        user_id = UserID(generate_uuid())
124
125
    if not created_at:
126
        created_at = datetime.utcnow()
127
128
    if not email_address:
129
        email_address = f'user{user_id}@users.test'
130
131
    user = user_creation_service.build_user(
132
        created_at, screen_name, email_address
133
    )
134
135
    user.id = user_id
136
    user.email_address_verified = email_address_verified
137
    user.initialized = initialized
138
    user.suspended = suspended
139
    user.deleted = deleted
140
    user.legacy_id = legacy_id
141
142
    if with_detail:
143
        detail = DbUserDetail(user=user)
144
        detail.first_names = first_names
145
        detail.last_name = last_name
146
        detail.date_of_birth = date_of_birth
147
        detail.country = country
148
        detail.zip_code = zip_code
149
        detail.city = city
150
        detail.street = street
151
        detail.phone_number = phone_number
152
153
    db.session.add(user)
154
155
    try:
156
        db.session.commit()
157
    except Exception as e:
158
        db.session.rollback()
159
        raise UserCreationFailed(e)
160
161
    if password is not None:
162
        password_service.create_password_hash(user.id, password)
163
164
    return user
165
166
167
def create_permissions(permission_ids: Iterable[PermissionID]) -> None:
168
    for permission_id in permission_ids:
169
        authz_service.create_permission(
170
            permission_id, permission_id, ignore_if_exists=True
171
        )
172
173
174
def create_role_with_permissions_assigned(
175
    role_id: RoleID, permission_ids: Iterable[PermissionID]
176
) -> None:
177
    role = authz_service.create_role(role_id, role_id, ignore_if_exists=True)
178
179
    for permission_id in permission_ids:
180
        authz_service.assign_permission_to_role(permission_id, role_id)
181
182
183
def create_party(
184
    brand_id: BrandID,
185
    party_id: PartyID = PartyID('acmecon-2014'),
186
    title: str = 'ACMECon 2014',
187
) -> Party:
188
    starts_at = datetime(2014, 10, 24, 16, 0)
189
    ends_at = datetime(2014, 10, 26, 13, 0)
190
191
    return party_service.create_party(
192
        party_id, brand_id, title, starts_at, ends_at
193
    )
194
195
196
def create_site(
197
    site_id: SiteID,
198
    brand_id: BrandID,
199
    *,
200
    title: Optional[str] = None,
201
    server_name: Optional[str] = None,
202
    enabled: bool = True,
203
    user_account_creation_enabled: bool = True,
204
    login_enabled: bool = True,
205
    party_id: Optional[PartyID] = None,
206
    board_id: Optional[BoardID] = None,
207
    storefront_id: Optional[StorefrontID] = None,
208
):
209
    if title is None:
210
        title = site_id
211
212
    if server_name is None:
213
        server_name = f'{site_id}.test'
214
215
    return site_service.create_site(
216
        site_id,
217
        title,
218
        server_name,
219
        brand_id,
220
        enabled=enabled,
221
        user_account_creation_enabled=user_account_creation_enabled,
222
        login_enabled=login_enabled,
223
        party_id=party_id,
224
        board_id=board_id,
225
        storefront_id=storefront_id,
226
    )
227
228
229
@contextmanager
230
def http_client(app: Flask, *, user_id: Optional[UserID] = None):
231
    """Provide an HTTP client.
232
233
    If a user ID is given, the client authenticates with the user's
234
    credentials.
235
    """
236
    client = app.test_client()
237
238
    if user_id is not None:
239
        _add_user_credentials_to_session(client, user_id)
240
241
    yield client
242
243
244
def _add_user_credentials_to_session(client, user_id: UserID) -> None:
245
    session_token = session_service.find_session_token_for_user(user_id)
246
    if session_token is None:
247
        raise Exception(f'Could not find session token for user ID "{user_id}"')
248
249
    with client.session_transaction() as session:
250
        session['user_id'] = str(user_id)
251
        session['user_auth_token'] = str(session_token.token)
252
253
254
def login_user(user_id: UserID) -> None:
255
    """Authenticate the user to create a session."""
256
    session_service.get_session_token(user_id)
257