Skip to content

Commit 3304da4

Browse files
committed
fix: soft-deleted된 event 설정 가능 문제 수정 및 is_document_valid 미충족 issuable 발급 시도 방어
1 parent 85a8fb8 commit 3304da4

4 files changed

Lines changed: 46 additions & 0 deletions

File tree

app/admin_api/serializers/shop/products.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
)
1212
from core.util.timespan import TimeSpan
1313
from document.models import IssuedDocument
14+
from event.models import Event
1415
from file.models import PublicFile
1516
from rest_framework import serializers
1617
from shop.order.models import OrderProductRelation
@@ -20,6 +21,9 @@
2021
class CategoryGroupAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, NestedFieldModelSerializer):
2122
class CategoryAdminSerializer(BaseAbstractSerializer, JsonSchemaSerializer, NestedModelSerializer):
2223
id = serializers.UUIDField(required=False, help_text="기존 Category 수정 시 PK 전달, 새로 추가 시 생략")
24+
event = serializers.PrimaryKeyRelatedField(
25+
queryset=Event.objects.filter_active(), allow_null=True, required=False
26+
)
2327

2428
class Meta:
2529
model = Category

app/admin_api/test/shop/ticket_info_admin_test.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22
from admin_api.test.helpers import CategoryGroupsAdminApi, OrdersAdminApi
3+
from model_bakery import baker
34
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
45
from shop.conftest import VALID_TICKET_INFO
56
from shop.order.models import TicketInfo
@@ -70,3 +71,29 @@ def test_admin_order_exposes_ticket_info(api_client, ticket_opr):
7071
assert response.status_code == HTTP_200_OK
7172
product_dto = next(p for p in response.json()["products"] if p["id"] == str(ticket_opr.id))
7273
assert product_dto["ticket_info"] == {**VALID_TICKET_INFO, "contribution_message": "응원"}
74+
75+
76+
# ==================== category.event — soft-deleted Event 연결 거부 ====================
77+
78+
79+
@pytest.mark.django_db
80+
def test_admin_category_rejects_soft_deleted_event(api_client, ticket_product):
81+
deleted_event = baker.make("event.Event", name="삭제된 행사")
82+
deleted_event.delete() # soft-delete → filter_active() 에서 제외되어야 함.
83+
category = ticket_product.category
84+
response = CategoryGroupsAdminApi(http_client=api_client).update(
85+
category.group_id,
86+
{
87+
"categories": [
88+
{
89+
"id": str(category.id),
90+
"name": category.name,
91+
"priority": category.priority,
92+
"event": str(deleted_event.id),
93+
}
94+
]
95+
},
96+
)
97+
assert response.status_code == HTTP_400_BAD_REQUEST
98+
category.refresh_from_db()
99+
assert category.event_id is None

app/document/issuable.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
class IssuableMixin:
1313
ISSUED_DOCUMENT_TYPE: ClassVar[str]
1414

15+
class NotIssuableError(Exception):
16+
"""is_document_valid() 조건을 충족하지 않는 issuable 에 대한 발급 시도 — 도메인 경계 가드."""
17+
1518
class DocumentStatus(models.TextChoices):
1619
not_issuable = "not_issuable", "발급 불가" # is_document_valid 조건 미충족
1720
issuable = "issuable", "발급 가능" # 조건 충족, 아직 미발급
@@ -38,6 +41,8 @@ def get_issued_document(self) -> IssuedDocument | None:
3841
def issue_document(self) -> IssuedDocument:
3942
from document.models import DocumentTemplate, IssuedDocument
4043

44+
if not self.is_document_valid():
45+
raise self.NotIssuableError(f"{type(self).__name__} 은 현재 발급 가능 상태가 아닙니다.")
4146
return IssuedDocument.objects.create(
4247
issuable=self,
4348
template=DocumentTemplate.objects.get_active(self.ISSUED_DOCUMENT_TYPE),

app/shop/order/test/opr_model_test.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,13 @@ def test_build_verify_display_maps_frozen_context():
8282
def test_is_document_downloadable_by_only_order_owner(used_ticket_opr, other_user):
8383
assert used_ticket_opr.is_document_downloadable_by(used_ticket_opr.order.user) is True
8484
assert used_ticket_opr.is_document_downloadable_by(other_user) is False
85+
86+
87+
@pytest.mark.django_db
88+
def test_issue_document_rejected_for_invalid_opr(used_ticket_opr):
89+
# event 연결을 끊으면 is_document_valid=False → 모델 경계 가드가 직접 발급(view 우회)을 막는다.
90+
category = used_ticket_opr.product.category
91+
category.event = None
92+
category.save(update_fields=["event"])
93+
with pytest.raises(OrderProductRelation.NotIssuableError):
94+
used_ticket_opr.get_or_issue_document()

0 commit comments

Comments
 (0)