Skip to content

Commit 81f769f

Browse files
committed
refactor: create_user에서 EmailAddress도 생성하도록 수정
1 parent a4b302c commit 81f769f

8 files changed

Lines changed: 79 additions & 49 deletions

File tree

‎app/admin_api/test/socialaccount_test.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ def regular_user(db) -> UserExt:
2222
SocialAccount.objects.create(
2323
user=user, provider="google", uid="alice-google-1", extra_data={"email": "alice@example.com"}
2424
)
25-
EmailAddress.objects.create(user=user, email="alice@example.com", verified=True, primary=True)
2625
return user
2726

2827

@@ -31,7 +30,6 @@ def multi_social_user(db) -> UserExt:
3130
user = UserExt.objects.create_user(username="bob", email="bob@example.com", password="x") # nosec: B106
3231
SocialAccount.objects.create(user=user, provider="google", uid="bob-google-1", extra_data={})
3332
SocialAccount.objects.create(user=user, provider="kakao", uid="bob-kakao-1", extra_data={})
34-
EmailAddress.objects.create(user=user, email="bob@example.com", verified=True, primary=True)
3533
return user
3634

3735

@@ -280,7 +278,7 @@ def test_email_address_list_filter_by_verified_and_primary(api_client, regular_u
280278
assert response.status_code == http.HTTPStatus.OK
281279
assert {row["email"] for row in response.json()["results"]} == {"alt@example.com"}
282280
# primary=true
283-
response = api_client.get(reverse("v1:admin-email-address-list"), {"primary": "true"})
281+
response = api_client.get(reverse("v1:admin-email-address-list"), {"primary": "true", "user": str(regular_user.id)})
284282
assert response.status_code == http.HTTPStatus.OK
285283
assert {row["email"] for row in response.json()["results"]} == {"alice@example.com"}
286284

‎app/admin_api/test/user_merge_test.py‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,12 @@
1616

1717
@pytest.fixture
1818
def source_user(db) -> UserExt:
19-
user = UserExt.objects.create_user(username="source", email="source@example.com")
20-
EmailAddress.objects.create(user=user, email="source@example.com", verified=True, primary=True)
21-
return user
19+
return UserExt.objects.create_user(username="source", email="source@example.com")
2220

2321

2422
@pytest.fixture
2523
def target_user(db) -> UserExt:
26-
user = UserExt.objects.create_user(username="target", email="target@example.com")
27-
EmailAddress.objects.create(user=user, email="target@example.com", verified=True, primary=True)
28-
return user
24+
return UserExt.objects.create_user(username="target", email="target@example.com")
2925

3026

3127
# ---- Auth -------------------------------------------------------------------
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import user.models.user
2+
from django.db.migrations import AlterModelManagers
3+
from django.db.migrations import Migration as DjangoMigrations
4+
5+
6+
class Migration(DjangoMigrations):
7+
dependencies = [("user", "0013_usermergeemailsnapshot")]
8+
operations = [AlterModelManagers(name="userext", managers=[("objects", user.models.user.UserExtManager())])]

‎app/user/models/user.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,24 @@
77
from core.const.system import SYSTEM_EMAIL, SYSTEM_USERNAME
88
from core.fields import EncryptedTextField
99
from core.scancode_mixin import ScanCodeMixin
10-
from django.contrib.auth.models import AbstractUser
10+
from django.contrib.auth.models import AbstractUser, UserManager
1111
from django.db import models
1212

1313

14+
class UserExtManager(UserManager):
15+
def _create_user(self, username, email, password, **extra_fields):
16+
user = super()._create_user(username, email, password, **extra_fields)
17+
if email:
18+
from allauth.account.models import EmailAddress
19+
20+
EmailAddress.objects.get_or_create(
21+
user=user,
22+
email=email.lower(),
23+
defaults={"verified": True, "primary": True},
24+
)
25+
return user
26+
27+
1428
class UserExt(ScanCodeMixin, AbstractUser):
1529
scancode_prefix = "user"
1630
scancode_uuid_field = "unique_id"
@@ -30,6 +44,8 @@ class UserExt(ScanCodeMixin, AbstractUser):
3044

3145
dooray_api_key = EncryptedTextField(key_setting_name="DOORAY_CRED_ENC_KEY", null=True, blank=True, editable=False)
3246

47+
objects = UserExtManager()
48+
3349
class Meta(AbstractUser.Meta):
3450
ordering = ["-date_joined"]
3551
indexes = [models.Index(fields=["unique_id"], name="userext_unique_id_idx")]

‎app/user/test/email_view_test.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
@pytest.fixture
1010
def user(db) -> UserExt:
1111
u = UserExt.objects.create_user(username="u", email="u@example.com")
12-
EmailAddress.objects.create(user=u, email="u@example.com", verified=True, primary=True)
1312
return u
1413

1514

@@ -139,7 +138,7 @@ def test_set_primary_unverified_rejected(client, user):
139138

140139
def test_action_on_other_users_email_rejected(client, user):
141140
other_user = UserExt.objects.create_user(username="o", email="o@example.com")
142-
other_email = EmailAddress.objects.create(user=other_user, email="o@example.com", verified=True)
141+
other_email = EmailAddress.objects.get(user=other_user)
143142
client.force_login(user)
144143
response = client.post(reverse("account-email-delete"), {"email_id": other_email.pk})
145144
assert response.status_code == http.HTTPStatus.BAD_REQUEST # request.user 로 스코프 → 못 찾음

‎app/user/test/merge_test.py‎

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from allauth.account.models import EmailAddress
66
from allauth.socialaccount.models import SocialAccount
77
from core.util.thread_local import thread_local
8+
from django.apps import apps
89
from django.contrib.contenttypes.models import ContentType
910
from shop.order.models import Order
1011
from user.models import UserExt
@@ -47,6 +48,26 @@ def _merge(source, target, actor=None) -> UserMergeHistory:
4748
return history
4849

4950

51+
def test_no_unhandled_user_m2m_relations(db):
52+
"""merge 는 M2M 를 이관하지 않는다 — user 를 건드리는 새 M2M 가 추가되면 조용히 누락되므로 여기서 잡는다."""
53+
allowed = {(UserExt._meta.label, "groups"), (UserExt._meta.label, "user_permissions")}
54+
found = {
55+
(model._meta.label, field.name)
56+
for model in apps.get_models()
57+
for field in model._meta.local_many_to_many
58+
if field.related_model is UserExt or model is UserExt
59+
}
60+
assert found <= allowed, f"merge 가 이관하지 않는 user M2M: {found - allowed}"
61+
62+
63+
def test_create_user_creates_verified_email(db):
64+
user = UserExt.objects.create_user(username="u", email="U@Example.com")
65+
66+
ea = EmailAddress.objects.get(user=user)
67+
assert ea.email == "u@example.com"
68+
assert ea.verified is True and ea.primary is True
69+
70+
5071
def test_merge_repoints_owned_business_fk(source_user, target_user):
5172
order = Order.objects.create(user=source_user, name="src-order")
5273

@@ -99,7 +120,6 @@ def test_untracked_model_is_repointed(source_user, target_user):
99120

100121
def test_allauth_accounts_are_moved(source_user, target_user):
101122
SocialAccount.objects.create(user=source_user, provider="google", uid="src-google", extra_data={})
102-
EmailAddress.objects.create(user=source_user, email="source@example.com", verified=True, primary=True)
103123

104124
_merge(source_user, target_user)
105125

@@ -130,9 +150,6 @@ def test_audit_fields_are_not_moved(source_user, target_user):
130150
def test_source_email_moves_and_demotes_when_target_has_primary(source_user, target_user):
131151
"""target 이 이미 primary 를 가지면, source 의 (중복 아닌) 이메일은 source 에 남기지 않고 target 으로
132152
옮기며 primary 를 강등한다 — 죽은 계정에 주소가 갇히지 않게."""
133-
EmailAddress.objects.create(user=source_user, email="source@example.com", verified=True, primary=True)
134-
EmailAddress.objects.create(user=target_user, email="target@example.com", verified=True, primary=True)
135-
136153
_merge(source_user, target_user)
137154

138155
assert not EmailAddress.objects.filter(user=source_user).exists()
@@ -143,9 +160,8 @@ def test_source_email_moves_and_demotes_when_target_has_primary(source_user, tar
143160

144161
def test_duplicate_email_consolidates_verification_and_demotes_source(source_user, target_user):
145162
"""양쪽이 같은 주소를 가지면 삭제 없이: target 사본을 verified 로 승격하고 source 사본은 미검증으로 강등해 남긴다."""
146-
EmailAddress.objects.create(user=source_user, email="dup@example.com", verified=True, primary=True)
163+
EmailAddress.objects.filter(user=source_user).update(email="dup@example.com") # source 의 유일 이메일을 dup 으로
147164
EmailAddress.objects.create(user=target_user, email="dup@example.com", verified=False, primary=False)
148-
EmailAddress.objects.create(user=target_user, email="target@example.com", verified=True, primary=True)
149165

150166
_merge(source_user, target_user)
151167

@@ -156,7 +172,8 @@ def test_duplicate_email_consolidates_verification_and_demotes_source(source_use
156172

157173
def test_target_primary_assigned_when_missing(source_user, target_user):
158174
"""규칙3: 병합 후 target 에 primary 가 없으면 하나(verified 우선)를 지정한다."""
159-
EmailAddress.objects.create(user=target_user, email="target@example.com", verified=True, primary=False)
175+
EmailAddress.objects.filter(user=source_user).delete() # source 이메일 없음
176+
EmailAddress.objects.filter(user=target_user).update(primary=False) # target 은 primary 없는 verified 이메일만
160177

161178
_merge(source_user, target_user)
162179

@@ -167,10 +184,8 @@ def test_target_primary_assigned_when_missing(source_user, target_user):
167184

168185
def test_unmerge_restores_email_state(source_user, target_user):
169186
"""규칙1(옮김+강등)·규칙2(검증통합) 모두 before-image replay 로 정확히 복원된다."""
170-
EmailAddress.objects.create(user=source_user, email="source@example.com", verified=True, primary=True)
171187
EmailAddress.objects.create(user=source_user, email="dup@example.com", verified=True, primary=False)
172188
EmailAddress.objects.create(user=target_user, email="dup@example.com", verified=False, primary=False)
173-
EmailAddress.objects.create(user=target_user, email="target@example.com", verified=True, primary=True)
174189

175190
merge = _merge(source_user, target_user)
176191
merge.unmerge()
@@ -282,42 +297,32 @@ def test_double_unmerge_rejected(source_user, target_user):
282297
# ---- assert_self_mergeable (본인 병합 사전검증) --------------------------------
283298

284299

285-
def _verified_email(user, email):
286-
return EmailAddress.objects.create(user=user, email=email, verified=True, primary=True)
287-
288-
289300
def test_self_mergeable_passes_when_both_verified(source_user, target_user):
290-
_verified_email(source_user, "source@example.com")
291-
_verified_email(target_user, "target@example.com")
292-
293-
UserMergeHistory.assert_self_mergeable(source_user, target_user) # no raise
301+
UserMergeHistory.assert_self_mergeable(source_user, target_user) # 둘 다 fixture 로 verified 이메일 보유 → no raise
294302

295303

296304
def test_self_mergeable_allows_email_less_source(source_user, target_user):
297-
_verified_email(target_user, "target@example.com") # source 는 이메일 없음 → 허용
305+
EmailAddress.objects.filter(user=source_user).delete() # source 는 이메일 없음 → 허용
298306

299307
UserMergeHistory.assert_self_mergeable(source_user, target_user) # no raise
300308

301309

302310
def test_self_mergeable_rejects_email_less_target(source_user, target_user):
303-
_verified_email(source_user, "source@example.com")
311+
EmailAddress.objects.filter(user=target_user).delete() # target 에 인증 이메일 없음 → 거부
304312

305313
with pytest.raises(ValueError):
306314
UserMergeHistory.assert_self_mergeable(source_user, target_user)
307315

308316

309317
def test_self_mergeable_rejects_unverified_target_email(source_user, target_user):
310-
_verified_email(source_user, "source@example.com")
311-
_verified_email(target_user, "target@example.com")
312318
EmailAddress.objects.create(user=target_user, email="extra@example.com", verified=False)
313319

314320
with pytest.raises(ValueError):
315321
UserMergeHistory.assert_self_mergeable(source_user, target_user)
316322

317323

318324
def test_self_mergeable_rejects_unverified_source_email(source_user, target_user):
319-
_verified_email(target_user, "target@example.com")
320-
EmailAddress.objects.create(user=source_user, email="source@example.com", verified=False)
325+
EmailAddress.objects.create(user=source_user, email="extra@example.com", verified=False)
321326

322327
with pytest.raises(ValueError):
323328
UserMergeHistory.assert_self_mergeable(source_user, target_user)

‎app/user/test/merge_view_test.py‎

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,12 @@
1313

1414
@pytest.fixture
1515
def source_user(db) -> UserExt:
16-
user = UserExt.objects.create_user(username="source", email="source@example.com")
17-
EmailAddress.objects.create(user=user, email="source@example.com", verified=True, primary=True)
18-
return user
16+
return UserExt.objects.create_user(username="source", email="source@example.com")
1917

2018

2119
@pytest.fixture
2220
def target_user(db) -> UserExt:
23-
user = UserExt.objects.create_user(username="target", email="target@example.com")
24-
EmailAddress.objects.create(user=user, email="target@example.com", verified=True, primary=True)
25-
return user
21+
return UserExt.objects.create_user(username="target", email="target@example.com")
2622

2723

2824
def _stash_source(client, source):
@@ -111,6 +107,24 @@ def test_start_lists_provider_forms(client, target_user):
111107
assert b'name="process" value="connect"' in response.content
112108

113109

110+
def test_merged_account_session_is_logged_out(client, source_user, target_user):
111+
UserMergeHistory.objects.create(source=source_user, target=target_user).merge()
112+
client.force_login(source_user) # 병합된(dead) 계정으로 남은 세션
113+
114+
response = client.get(reverse("account-home"))
115+
116+
assert response.status_code == 302
117+
assert "merged=1" in response["Location"]
118+
assert "_auth_user_id" not in client.session
119+
120+
121+
def test_login_shows_merged_notice(client, db):
122+
response = client.get(reverse("account-login") + "?merged=1")
123+
124+
assert response.status_code == 200
125+
assert "병합".encode() in response.content
126+
127+
114128
# ---- AccountLoginView ---------------------------------------------------------
115129

116130

@@ -152,9 +166,7 @@ def test_home_links_to_merge(client, target_user):
152166

153167
@pytest.fixture
154168
def password_user(db) -> UserExt:
155-
user = UserExt.objects.create_user(username="pw", email="pw@example.com", password="s3cret-pw-123") # nosec B106
156-
EmailAddress.objects.create(user=user, email="pw@example.com", verified=True, primary=True)
157-
return user
169+
return UserExt.objects.create_user(username="pw", email="pw@example.com", password="s3cret-pw-123") # nosec B106
158170

159171

160172
def test_password_login_page_renders_form(client, db):

‎app/user/test/password_view_test.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import pytest
55
from allauth.account.forms import default_token_generator
6-
from allauth.account.models import EmailAddress
76
from allauth.account.utils import user_pk_to_url_str
87
from django.urls import reverse
98
from user.models import UserExt
@@ -15,9 +14,7 @@
1514

1615
@pytest.fixture
1716
def user(db) -> UserExt:
18-
u = UserExt.objects.create_user(username="u", email="u@example.com", password=PASSWORD)
19-
EmailAddress.objects.create(user=u, email="u@example.com", verified=True, primary=True)
20-
return u
17+
return UserExt.objects.create_user(username="u", email="u@example.com", password=PASSWORD)
2118

2219

2320
@pytest.fixture
@@ -26,7 +23,6 @@ def social_user(db) -> UserExt:
2623
u = UserExt.objects.create_user(username="s", email="s@example.com")
2724
u.set_unusable_password()
2825
u.save()
29-
EmailAddress.objects.create(user=u, email="s@example.com", verified=True, primary=True)
3026
return u
3127

3228

0 commit comments

Comments
 (0)