Skip to content

Commit 4811cd7

Browse files
committed
chore: 배포 액션 실패 이슈 수정
1 parent cd09bf1 commit 4811cd7

6 files changed

Lines changed: 42 additions & 17 deletions

File tree

‎.github/workflows/test.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ jobs:
4848

4949
- uses: astral-sh/setup-uv@v5
5050
with:
51+
# 러너 기본 파이썬(3.12.3)은 RFC 2047 헤더 폴딩 버그가 있다. 배포 이미지(python:3.12-slim)와 맞춘다.
52+
python-version: '3.12'
5153
enable-cache: true
5254
ignore-nothing-to-cache: true
5355

@@ -92,6 +94,8 @@ jobs:
9294

9395
- uses: astral-sh/setup-uv@v5
9496
with:
97+
# 러너 기본 파이썬(3.12.3)은 RFC 2047 헤더 폴딩 버그가 있다. 배포 이미지(python:3.12-slim)와 맞춘다.
98+
python-version: '3.12'
9599
enable-cache: true
96100
ignore-nothing-to-cache: true
97101

‎app/admin_api/serializers/shop/orders.py‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -313,16 +313,6 @@ class Meta:
313313
model = OrderProductRelationTag
314314
fields = COMMON_ADMIN_FIELDS + ("code", "name", "priority")
315315

316-
def validate_code(self, value: str) -> str:
317-
duplicated = (
318-
OrderProductRelationTag.objects.filter_active()
319-
.filter(code=value)
320-
.exclude(pk=self.instance.pk if self.instance else None)
321-
)
322-
if duplicated.exists():
323-
raise serializers.ValidationError("이미 있는 코드입니다.")
324-
return value
325-
326316

327317
class OrderProductRelationTagAssignResultSerializer(serializers.Serializer):
328318
affected = serializers.IntegerField()

‎app/admin_api/test/shop/order_product_notifications_api_test.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,24 @@ def test_opr_notification_preview_prefers_ticket_info_over_customer_info(api_cli
116116
assert recipient["context"]["customer_email"] == "customer@example.com"
117117

118118

119+
@pytest.mark.django_db
120+
def test_opr_notification_preview_skips_participant_without_channel_contact(api_client, opr_email_template, ticket_opr):
121+
# 참가자 정보가 있으면 주문자로 되돌아가지 않는다 — 엉뚱한 사람에게 티켓 QR 이 가면 안 된다.
122+
TicketInfo.objects.create(
123+
order_product_relation=ticket_opr,
124+
name="참가자",
125+
phone="01099998888",
126+
email="",
127+
)
128+
129+
response = OrderProductNotificationsAdminApi(http_client=api_client).preview(
130+
{"channel": "email", "template_id": str(opr_email_template.id)}
131+
)
132+
133+
assert response.status_code == HTTP_200_OK
134+
assert response.json()["recipients"] == []
135+
136+
119137
@pytest.mark.django_db
120138
def test_opr_notification_preview_falls_back_to_customer_info(api_client, opr_email_template, ticket_opr):
121139
response = OrderProductNotificationsAdminApi(http_client=api_client).preview(

‎app/admin_api/test/shop/order_product_relation_tags_api_test.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,16 @@ def test_order_detail_exposes_tags(api_client, speaker_tag, order_factory):
111111
assert body["products"][0]["tags"] == [{"id": str(speaker_tag.id), "code": "speaker", "name": "발표자"}]
112112

113113

114+
@pytest.mark.django_db
115+
def test_assign_rejects_invalid_filter_value(api_client, speaker_tag, order_factory):
116+
order_factory(status="completed")
117+
118+
response = api_client.post(f"{_assign_url(speaker_tag.id)}?first_paid_at_after=어제")
119+
120+
assert response.status_code == HTTP_400_BAD_REQUEST
121+
assert speaker_tag.order_product_relations.count() == 0
122+
123+
114124
@pytest.mark.django_db
115125
def test_assign_rejects_blank_filter_value(api_client, speaker_tag, order_factory):
116126
# django-filter 는 빈 값을 무시하므로 `?id=` 가 전체 태깅이 되면 안 된다.

‎app/shop/order/models.py‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,7 @@ def customer_info_or_none(self) -> CustomerInfo | None:
478478
def participant_info(self) -> TicketInfo | CustomerInfo | None:
479479
return self.ticket_info_or_none or self.customer_info_or_none
480480

481-
def build_notification_context(self, participant: TicketInfo | CustomerInfo | None = None) -> dict:
482-
if participant is None:
483-
participant = self.participant_info
481+
def build_notification_context(self, participant: TicketInfo | CustomerInfo | None) -> dict:
484482
name = (getattr(participant, "name", "")) or ""
485483
organization = (getattr(participant, "organization", "")) or ""
486484
event = self.product.category.event

‎app/shop/order/test/scancode_api_test.py‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
from shop.test.helpers import ScanCodeApi
1212

1313

14+
def _tamper(token: str) -> str:
15+
# 마지막 글자를 그대로 되돌려 쓰면 원본과 같아져 테스트가 간헐적으로 통과한다.
16+
return token[:-1] + ("A" if token[-1] != "A" else "B")
17+
18+
1419
@pytest.mark.django_db
1520
def test_scancode_rejects_missing_token(anon_client):
1621
response = ScanCodeApi(http_client=anon_client).list()
@@ -72,7 +77,7 @@ def test_scancode_opr_token_with_invalid_salt_rejects(anon_client, order_factory
7277
completed_order = order_factory(status="completed")
7378
opr = completed_order.products.first()
7479
# token 의 salt 부분 변조 — from_scancode_token 이 None 반환 → 403.
75-
tampered = opr.scancode_token[:-1] + ("A" if opr.scancode_token[-1] != "A" else "B")
80+
tampered = _tamper(opr.scancode_token)
7681
response = ScanCodeApi(http_client=anon_client).list({"token": tampered})
7782
assert response.status_code == HTTP_403_FORBIDDEN
7883

@@ -149,7 +154,7 @@ def test_scancode_token_rejected_after_old_salt_removed(anon_client, order_facto
149154
@pytest.mark.django_db
150155
def test_scancode_error_page_shows_both_languages_and_guide(anon_client, order_factory):
151156
order = order_factory(status="completed")
152-
tampered = order.scancode_token[:-1] + "A"
157+
tampered = _tamper(order.scancode_token)
153158

154159
response = anon_client.get(reverse("v1:scancode-list"), {"token": tampered}, HTTP_ACCEPT_LANGUAGE="ko-KR,ko;q=0.9")
155160
body = response.content.decode()
@@ -162,7 +167,7 @@ def test_scancode_error_page_shows_both_languages_and_guide(anon_client, order_f
162167
@pytest.mark.django_db
163168
def test_scancode_error_page_is_english_for_english_client(anon_client, order_factory):
164169
order = order_factory(status="completed")
165-
tampered = order.scancode_token[:-1] + "A"
170+
tampered = _tamper(order.scancode_token)
166171

167172
response = anon_client.get(reverse("v1:scancode-list"), {"token": tampered}, HTTP_ACCEPT_LANGUAGE="en-US,en;q=0.9")
168173
body = response.content.decode()
@@ -180,7 +185,7 @@ def test_scancode_renders_html_even_when_client_asks_for_json(anon_client, order
180185
assert ok.status_code == HTTP_200_OK
181186
assert ok["Content-Type"].startswith("text/html")
182187

183-
tampered = order.scancode_token[:-1] + "A"
188+
tampered = _tamper(order.scancode_token)
184189
error = anon_client.get(reverse("v1:scancode-list"), {"token": tampered}, HTTP_ACCEPT="application/json")
185190
assert error.status_code == HTTP_404_NOT_FOUND
186191
assert SCANCODE_MESSAGES["order_not_found"]["ko"] in error.content.decode()

0 commit comments

Comments
 (0)