From 66e62089d23135369b7303b0f80ea0fafb59149f Mon Sep 17 00:00:00 2001 From: StoneHee99 Date: Sat, 27 Sep 2025 21:25:20 +0900 Subject: [PATCH 1/5] =?UTF-8?q?chore:=20e2e=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9E=84=EC=9D=84=20=EB=AA=85=EC=8B=9C=20(=EC=8B=9C=EB=82=98?= =?UTF-8?q?=EB=A6=AC=EC=98=A4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/resources/features/kiosk-product.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/features/kiosk-product.feature b/src/test/resources/features/kiosk-product.feature index 253439b..7893ad3 100644 --- a/src/test/resources/features/kiosk-product.feature +++ b/src/test/resources/features/kiosk-product.feature @@ -1,4 +1,4 @@ -Feature: 키오스크 앱 상품 조회 +Feature: 키오스크 앱 상품 조회 E2E 테스트 Scenario: 상품 조회를 요청하면 상품 목록이 잘 조회된다. When 키오스크 앱으로 상품 조회 요청을 보낸다 From 32ab59c255f715de2eba96728aebc2d4c87b9f09 Mon Sep 17 00:00:00 2001 From: StoneHee99 Date: Sat, 27 Sep 2025 21:31:57 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20compose=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0=20(YAML=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/docker-compose.yml | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 71cbae1..68fc20a 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -2,6 +2,15 @@ networks: atdd-net: external: true +x-db-env: &db_env + SPRING_DATASOURCE_URL: "jdbc:mysql://atdd-db:3306/atdd?useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul" + SPRING_DATASOURCE_USERNAME: "root" + SPRING_DATASOURCE_PASSWORD: "secret" + +x-wait-db: &wait_db_cmd > + sh -c "until nc -z atdd-db 3306; do echo 'waiting db...'; sleep 2; done; + java $JAVA_OPTS -jar /app/app.jar" + services: kiosk: build: @@ -20,13 +29,8 @@ services: KIOSK_ADMIN_AUTH_PASSWORD: "admin123" KIOSK_ADMIN_AUTH_COOKIE_NAME: "AUTH_TOKEN" RESERVATION_BASE_URL: "http://reservation:8080" - SPRING_DATASOURCE_URL: "jdbc:mysql://atdd-db:3306/atdd?useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul" - SPRING_DATASOURCE_USERNAME: "root" - SPRING_DATASOURCE_PASSWORD: "secret" - - command: > - sh -c "until nc -z atdd-db 3306; do echo 'waiting db...'; sleep 2; done; - java $JAVA_OPTS -jar /app/app.jar" + <<: *db_env + command: *wait_db_cmd admin: build: @@ -41,12 +45,8 @@ services: JAVA_OPTS: "-Xms256m -Xmx512m" RESERVATION_BASE_URL: "http://reservation:8080" KIOSK_BASE_URL: "http://kiosk:8080" - SPRING_DATASOURCE_URL: "jdbc:mysql://atdd-db:3306/atdd?useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul" - SPRING_DATASOURCE_USERNAME: "root" - SPRING_DATASOURCE_PASSWORD: "secret" - command: > - sh -c "until nc -z atdd-db 3306; do echo 'waiting db...'; sleep 2; done; - java $JAVA_OPTS -jar /app/app.jar" + <<: *db_env + command: *wait_db_cmd reservation: build: @@ -61,9 +61,5 @@ services: JAVA_OPTS: "-Xms256m -Xmx512m" ADMIN_BASE_URL: "http://admin:8080" KIOSK_BASE_URL: "http://kiosk:8080" - SPRING_DATASOURCE_URL: "jdbc:mysql://atdd-db:3306/atdd?useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true&serverTimezone=Asia/Seoul" - SPRING_DATASOURCE_USERNAME: "root" - SPRING_DATASOURCE_PASSWORD: "secret" - command: > - sh -c "until nc -z atdd-db 3306; do echo 'waiting db...'; sleep 2; done; - java $JAVA_OPTS -jar /app/app.jar" + <<: *db_env + command: *wait_db_cmd From 1163d7e49322fe602e992a351d9d83e936ab4fed Mon Sep 17 00:00:00 2001 From: StoneHee99 Date: Sat, 27 Sep 2025 21:58:35 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20payments=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20(wiremock)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/docker-compose.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 68fc20a..f18c49a 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -29,6 +29,8 @@ services: KIOSK_ADMIN_AUTH_PASSWORD: "admin123" KIOSK_ADMIN_AUTH_COOKIE_NAME: "AUTH_TOKEN" RESERVATION_BASE_URL: "http://reservation:8080" + KIOSK_PAYMENT_BASE_URL: "http://payments-mock:8080" + KIOSK_PAYMENT_SECRET_KEY: "test_sk_dummy" <<: *db_env command: *wait_db_cmd @@ -63,3 +65,16 @@ services: KIOSK_BASE_URL: "http://kiosk:8080" <<: *db_env command: *wait_db_cmd + + payments-mock: + image: wiremock/wiremock:3.13.1 + container_name: payments-mock + networks: [ atdd-net ] + ports: + - "9090:8080" + volumes: + - ./wiremock:/home/wiremock + command: + - "--global-response-templating" + - "--verbose" + From f992fbf1e187d85ca34e6b66c74792cf2b53aec4 Mon Sep 17 00:00:00 2001 From: StoneHee99 Date: Sat, 27 Sep 2025 22:56:46 +0900 Subject: [PATCH 4/5] =?UTF-8?q?test:=20kiosk=20=EA=B2=B0=EC=A0=9C=20e2e=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mappings/payment-20-confirm-success.json | 30 +++++++++ .../payment-21-confirm-amount-mismatch.json | 14 ++++ .../tests/steps/KioskPaymentStepDef.java | 64 +++++++++++++++++++ .../resources/features/kiosk-payment.feature | 11 ++++ 4 files changed, 119 insertions(+) create mode 100644 infra/wiremock/mappings/payment-20-confirm-success.json create mode 100644 infra/wiremock/mappings/payment-21-confirm-amount-mismatch.json create mode 100644 src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java create mode 100644 src/test/resources/features/kiosk-payment.feature diff --git a/infra/wiremock/mappings/payment-20-confirm-success.json b/infra/wiremock/mappings/payment-20-confirm-success.json new file mode 100644 index 0000000..2bb32a5 --- /dev/null +++ b/infra/wiremock/mappings/payment-20-confirm-success.json @@ -0,0 +1,30 @@ +{ + "priority": 20, + "request": { + "method": "POST", + "urlPath": "/v1/payments/confirm", + "headers": { + "Authorization": { "equalTo": "Basic dGVzdF9za19kdW1teTo=" }, + "Content-Type": { "contains": "application/json" } + }, + "bodyPatterns": [ + { "matchesJsonPath": "$.paymentKey" }, + { "matchesJsonPath": "$.orderId" }, + { "matchesJsonPath": "$.amount" } + ] + }, + "response": { + "status": 200, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { + "paymentKey": "{{jsonPath request.body '$.paymentKey'}}", + "orderId": "{{jsonPath request.body '$.orderId'}}", + "method": "CARD", + "approvedAt": "2024-01-01T12:34:56Z", + "totalAmount": "{{jsonPath request.body '$.amount'}}", + "status": "APPROVED", + "receipt": { "url": "https://pay.local/receipts/{{jsonPath request.body '$.paymentKey'}}" } + }, + "transformers": ["response-template"] + } +} diff --git a/infra/wiremock/mappings/payment-21-confirm-amount-mismatch.json b/infra/wiremock/mappings/payment-21-confirm-amount-mismatch.json new file mode 100644 index 0000000..3e06dd8 --- /dev/null +++ b/infra/wiremock/mappings/payment-21-confirm-amount-mismatch.json @@ -0,0 +1,14 @@ +{ + "priority": 19, + "request": { + "method": "POST", + "urlPath": "/v1/payments/confirm", + "headers": { "Authorization": { "equalTo": "Basic dGVzdF9za19kdW1teTo=" } }, + "bodyPatterns": [ { "matchesJsonPath": "$[?(@.amount == 99999)]" } ] + }, + "response": { + "status": 409, + "headers": { "Content-Type": "application/json" }, + "jsonBody": { "code": "AMOUNT_MISMATCH", "message": "amount/orderId mismatch" } + } +} diff --git a/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java b/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java new file mode 100644 index 0000000..0fc7a1a --- /dev/null +++ b/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java @@ -0,0 +1,64 @@ +package com.camping.tests.steps; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +import io.cucumber.java.PendingException; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import io.restassured.response.Response; +import java.util.List; +import java.util.Map; + +public class KioskPaymentStepDef { + + private int amount; + private Response response; + + @Given("결제 금액이 {int}원인 장바구니가 있다") + public void 결제금액이원인장바구니가있다(int amount) { + this.amount = amount; + } + + @When("결제키 {string} 와 주문번호 {string} 로 결제 승인을 요청한다") + public void 결제키와주문번호로결제승인을요청한다(String paymentKey, String orderId) { + Map body = Map.of( + "paymentKey", paymentKey, + "orderId", orderId, + "amount", amount, + "items", List.of(Map.of( + "productId", 1, + "quantity", 1, + "lineTotal", amount + )) + ); + + response = given() + .contentType("application/json") + .body(body) + .when().post("http://localhost:8080/api/payments/confirm") + .then().extract().response(); + } + + + @Then("결제가 성공하며 결제 금액은 {int}원이다") + public void 결제가성공하며결제금액은원이다(int expectedAmount) { + assertThat(response.statusCode()).isEqualTo(200); + boolean success = response.jsonPath().getBoolean("success"); + int paid = response.jsonPath().getInt("paidAmount"); + assertThat(success).isTrue(); + assertThat(paid).isEqualTo(expectedAmount); + } + + @Then("결제가 실패한다.") + public void 결제가실패한다() { + int sc = response.statusCode(); + if (sc >= 200 && sc < 300) { + boolean success = response.jsonPath().getBoolean("success"); + assertThat(success).isFalse(); + } else { + assertThat(sc).isBetween(400, 599); + } + } +} diff --git a/src/test/resources/features/kiosk-payment.feature b/src/test/resources/features/kiosk-payment.feature new file mode 100644 index 0000000..538b5ff --- /dev/null +++ b/src/test/resources/features/kiosk-payment.feature @@ -0,0 +1,11 @@ +Feature: 키오스크 결제 승인 E2E 테스트 + + Scenario: 결제 승인을 요청하면 결제가 정상 승인된다. + Given 결제 금액이 10000원인 장바구니가 있다 + When 결제키 "pay_123" 와 주문번호 "ord_123" 로 결제 승인을 요청한다 + Then 결제가 성공하며 결제 금액은 10000원이다 + + Scenario: 결제 승인을 요청하면 결제가 실패한다. + Given 결제 금액이 99999원인 장바구니가 있다 + When 결제키 "pay_fail" 와 주문번호 "ord_fail" 로 결제 승인을 요청한다 + Then 결제가 실패한다. \ No newline at end of file From f8c9359115a643bc371cf06b89d0d23cdbbc53a8 Mon Sep 17 00:00:00 2001 From: StoneHee99 Date: Sat, 27 Sep 2025 23:01:20 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camping/tests/steps/KioskPaymentStepDef.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java b/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java index 0fc7a1a..5da296e 100644 --- a/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java +++ b/src/test/java/com/camping/tests/steps/KioskPaymentStepDef.java @@ -10,6 +10,7 @@ import io.restassured.response.Response; import java.util.List; import java.util.Map; +import org.apache.http.HttpStatus; public class KioskPaymentStepDef { @@ -44,7 +45,7 @@ public class KioskPaymentStepDef { @Then("결제가 성공하며 결제 금액은 {int}원이다") public void 결제가성공하며결제금액은원이다(int expectedAmount) { - assertThat(response.statusCode()).isEqualTo(200); + assertThat(response.statusCode()).isEqualTo(HttpStatus.SC_OK); boolean success = response.jsonPath().getBoolean("success"); int paid = response.jsonPath().getInt("paidAmount"); assertThat(success).isTrue(); @@ -53,12 +54,8 @@ public class KioskPaymentStepDef { @Then("결제가 실패한다.") public void 결제가실패한다() { - int sc = response.statusCode(); - if (sc >= 200 && sc < 300) { - boolean success = response.jsonPath().getBoolean("success"); - assertThat(success).isFalse(); - } else { - assertThat(sc).isBetween(400, 599); - } + assertThat(response.statusCode()).isEqualTo(HttpStatus.SC_OK); + boolean success = response.jsonPath().getBoolean("success"); + assertThat(success).isFalse(); } }