|
13 | 13 | _NON_WHITELISTED_IP = "5.6.7.8" |
14 | 14 |
|
15 | 15 |
|
16 | | -def _post_webhook(*, merchant_uid: str, ip: str, status: str = "paid", imp_uid: str = "imp_x") -> Response: |
| 16 | +def _post_webhook( |
| 17 | + *, |
| 18 | + merchant_uid: str, |
| 19 | + ip: str | None = None, |
| 20 | + xff: str | None = None, |
| 21 | + x_real_ip: str | None = None, |
| 22 | + status: str = "paid", |
| 23 | + imp_uid: str = "imp_x", |
| 24 | +) -> Response: |
| 25 | + meta: dict[str, str] = {} |
| 26 | + if ip is not None: |
| 27 | + meta["REMOTE_ADDR"] = ip |
| 28 | + if xff is not None: |
| 29 | + meta["HTTP_X_FORWARDED_FOR"] = xff |
| 30 | + if x_real_ip is not None: |
| 31 | + meta["HTTP_X_REAL_IP"] = x_real_ip |
17 | 32 | return APIClient().post( |
18 | 33 | path=reverse("v1:payment_histories-list"), |
19 | 34 | data=make_webhook_payload(merchant_uid=merchant_uid, status=status, imp_uid=imp_uid), |
20 | 35 | format="json", |
21 | | - REMOTE_ADDR=ip, |
| 36 | + **meta, |
22 | 37 | ) |
23 | 38 |
|
24 | 39 |
|
@@ -64,11 +79,26 @@ def test_ip_allowlist_respects_debug_bypass( |
64 | 79 | assert not PaymentHistory.objects.filter(order=pending_order).exists() |
65 | 80 |
|
66 | 81 |
|
| 82 | +@pytest.mark.parametrize( |
| 83 | + "post_kwargs", |
| 84 | + [ |
| 85 | + # 테스트 환경에서 REMOTE_ADDR 만 직접 지정 (프록시 없음). |
| 86 | + {"ip": WEBHOOK_WHITELISTED_IP}, |
| 87 | + # nginx 뒤 운영: REMOTE_ADDR 은 nginx 컨테이너 IP, 실제 IP 는 X-Forwarded-For 만. |
| 88 | + {"ip": _NON_WHITELISTED_IP, "xff": WEBHOOK_WHITELISTED_IP}, |
| 89 | + # 프록시가 X-Real-IP 만 채우는 변형. |
| 90 | + {"ip": _NON_WHITELISTED_IP, "x_real_ip": WEBHOOK_WHITELISTED_IP}, |
| 91 | + # nginx 가 두 헤더 모두 채우는 일반적인 구성. |
| 92 | + {"ip": _NON_WHITELISTED_IP, "xff": WEBHOOK_WHITELISTED_IP, "x_real_ip": WEBHOOK_WHITELISTED_IP}, |
| 93 | + # 다중 hop X-Forwarded-For — leftmost(원 클라이언트) 가 화이트리스트면 통과. |
| 94 | + {"ip": _NON_WHITELISTED_IP, "xff": f"{WEBHOOK_WHITELISTED_IP}, 10.0.0.1, 10.0.0.2"}, |
| 95 | + ], |
| 96 | +) |
67 | 97 | @pytest.mark.django_db |
68 | | -def test_accepts_request_from_whitelisted_ip(mock_portone_find_payment_info, order_factory): |
| 98 | +def test_accepts_request_from_whitelisted_ip(post_kwargs, mock_portone_find_payment_info, order_factory): |
69 | 99 | pending_order = order_factory(status="prepared") |
70 | 100 | mock_portone_find_payment_info.return_value = make_portone_payment_info(order=pending_order) |
71 | | - response = _post_webhook(merchant_uid=pending_order.merchant_uid, ip=WEBHOOK_WHITELISTED_IP) |
| 101 | + response = _post_webhook(merchant_uid=pending_order.merchant_uid, **post_kwargs) |
72 | 102 | assert response.status_code == HTTP_200_OK |
73 | 103 | assert response.json() == {"status": "success", "message": "일반 결제 성공"} |
74 | 104 | assert PaymentHistory.objects.filter( |
|
0 commit comments