From 07878e4acfb179b62c2d437075658cb95eb696d4 Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 09:24:24 +0900 Subject: [PATCH 1/7] chore: update database configuration in application.yaml --- src/main/resources/application.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4aa3c00..8e7bd27 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -3,7 +3,7 @@ spring: name: reservation-service datasource: - url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:reservation_db} + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:michelet_db} username: ${DB_USER:admin} password: ${DB_PASSWORD:admin} driver-class-name: org.postgresql.Driver @@ -14,7 +14,13 @@ spring: properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect - default_schema: public + default_schema: reservation_db + format_sql: true + jdbc.time_zone: Asia/Seoul + jdbc: + batch_size: 30 + order_inserts: true + order_updates: true show-sql: false open-in-view: false From 5d515d38efd78f19e153816864010ef13a04ea91 Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 14:56:35 +0900 Subject: [PATCH 2/7] chore: update Feign client configurations and disable stubs in YAML --- .../reservation/infrastructure/client/TimeSlotClient.java | 1 - .../reservation/infrastructure/client/WaitingClient.java | 2 +- src/main/resources/application-local.yaml | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/michelet/reservation/infrastructure/client/TimeSlotClient.java b/src/main/java/com/michelet/reservation/infrastructure/client/TimeSlotClient.java index 785a9d2..7a8958a 100644 --- a/src/main/java/com/michelet/reservation/infrastructure/client/TimeSlotClient.java +++ b/src/main/java/com/michelet/reservation/infrastructure/client/TimeSlotClient.java @@ -10,7 +10,6 @@ @FeignClient( name = "timeslot-service" - , url = "${feign.timeslot-service.url}" // , configuration = InternalSecretFeignConfig.class // todo : mvp 이후 InternalSecret 적용여부 판단 ) diff --git a/src/main/java/com/michelet/reservation/infrastructure/client/WaitingClient.java b/src/main/java/com/michelet/reservation/infrastructure/client/WaitingClient.java index bb7d9b3..a1efa8d 100644 --- a/src/main/java/com/michelet/reservation/infrastructure/client/WaitingClient.java +++ b/src/main/java/com/michelet/reservation/infrastructure/client/WaitingClient.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestParam; -@FeignClient(name = "waiting-service", url = "${feign.waiting-service.url}") +@FeignClient(name = "waiting-service") public interface WaitingClient { @GetMapping("/internal/waitings/verify-token") diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index a821043..27b6a53 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -3,10 +3,10 @@ spring: bootstrap-servers: localhost:9092 waiting: - stub: true + stub: false timeslot: - stub: true + stub: false kafka: stub: true From b185aaf0cda22447ca352a796aff86d25eba8404 Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 17:01:28 +0900 Subject: [PATCH 3/7] chore: update database configuration in application.yaml --- src/main/resources/application.yaml | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 8e7bd27..65ae85d 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -10,11 +10,11 @@ spring: jpa: hibernate: - ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO:update} + ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO:create} properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect - default_schema: reservation_db + default_schema: reservation_service format_sql: true jdbc.time_zone: Asia/Seoul jdbc: @@ -41,20 +41,16 @@ spring: config: enabled: false discovery: - enabled: false -#eureka: -# client: -# service-url: -# defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:19090}/eureka/ + enabled: true -internal: - secret: ${INTERNAL_SECRET:dev-secret} +eureka: + client: + service-url: + defaultZone: http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}/eureka/ -feign: - timeslot-service: - url: ${FEIGN_TIMESLOT_SERVICE_URL:http://localhost:19400} - waiting-service: - url: ${FEIGN_WAITING_SERVICE_URL:http://localhost:19300} +internal: + auth: + secret: ${INTERNAL_AUTH_SECRET:internal-secret} server: port: 19500 \ No newline at end of file From 1404086d2dbe09b83150e81ef82937817704779e Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 17:01:51 +0900 Subject: [PATCH 4/7] chore: remove cloud config enabled setting from application.yaml --- src/main/resources/application.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 65ae85d..b4f98c2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -38,8 +38,6 @@ spring: spring.json.add.type.headers: false cloud: - config: - enabled: false discovery: enabled: true From 2c2646a11a46bbfb1089d2da0abaf5146fa76c1e Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 23:48:43 +0900 Subject: [PATCH 5/7] =?UTF-8?q?chore:=20add=20local=20HTTP=20client=20test?= =?UTF-8?q?=20guide=20and=20related=20setup=20files=20=EC=98=88=EC=95=BD?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=95=9C=EC=A0=95.=20=EB=8B=A4=EB=A5=B8?= =?UTF-8?q?=20=ED=8C=8C=ED=8A=B8=EC=9D=98=20=EA=B2=BD=EC=9A=B0=20=EB=8B=A8?= =?UTF-8?q?=EC=88=9C=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=EC=83=81=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=EB=90=98=EB=8A=94=20=EB=82=B4=EB=B6=80=20api?= =?UTF-8?q?=20=ED=98=B8=EC=B6=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http-tests/LOCAL_TEST_GUIDE.md | 125 ++++++++++ http-tests/http-client.env.json | 18 ++ http-tests/scenario1a_setup.http | 129 ++++++++++ http-tests/scenario1b_reservation.http | 124 ++++++++++ http-tests/scenario2_checkin.http | 44 ++++ http-tests/scenario3_history_check.http | 27 +++ http-tests/sql/reset_test_data.sql | 38 +++ http-tests/sql/seed_test_data.sql | 298 ++++++++++++++++++++++++ http-tests/start-local.sh | 78 +++++++ 9 files changed, 881 insertions(+) create mode 100644 http-tests/LOCAL_TEST_GUIDE.md create mode 100644 http-tests/http-client.env.json create mode 100644 http-tests/scenario1a_setup.http create mode 100644 http-tests/scenario1b_reservation.http create mode 100644 http-tests/scenario2_checkin.http create mode 100644 http-tests/scenario3_history_check.http create mode 100644 http-tests/sql/reset_test_data.sql create mode 100644 http-tests/sql/seed_test_data.sql create mode 100755 http-tests/start-local.sh diff --git a/http-tests/LOCAL_TEST_GUIDE.md b/http-tests/LOCAL_TEST_GUIDE.md new file mode 100644 index 0000000..4ac5953 --- /dev/null +++ b/http-tests/LOCAL_TEST_GUIDE.md @@ -0,0 +1,125 @@ +# Local HTTP Client Test Guide + +IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. +모든 서비스가 Eureka에 등록되고, FeignClient는 `lb://` 로드밸런싱으로 내부 서비스를 호출합니다. + +--- + +## 환경 설정 + +### 서비스 설정 및 Java 코드 변경 + +# waiting-service + application.yml + - Eureka 클라이언트 설정 추가 (서비스 등록 및 검색) + - port 19200 + build.gradle + - Spring Cloud Netflix Eureka Client 의존성 추가 +# user-service + application.yml 설정 + + eureka: + client: + service-url: + defaultZone: http://localhost:${EUREKA_PORT:8761}/eureka/ + + spring: + datasource: + url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432}/${POSTGRES_DB:michelet_db} + username: ${POSTGRES_USER:admin} + password: ${POSTGRES_PASSWORD:admin} + driver-class-name: org.postgresql.Driver + data: + redis: + host: localhost + port: ${REDIS_PORT:6379} + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + default_schema: user_service + format_sql: true + show-sql: true + +# timeslot-service + application.yml 설정 + + eureka: + client: + service-url: + defaultZone: http://localhost:${EUREKA_PORT:8761}/eureka/ + + spring: + datasource: + url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432/${POSTGRES_DB:michelet_db} + username: ${POSTGRES_USER:admin} + password: ${POSTGRES_PASSWORD:admin} + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + default_schema: timeslot_service + format_sql: true + show-sql: true +# restaurant-service + application.yml 설정 + + eureka: + client: + service-url: + defaultZone: http://localhost:${EUREKA_PORT:8761}/eureka/ + + spring: + datasource: + url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432/${POSTGRES_DB:michelet_db} + username: ${POSTGRES_USER:admin} + password: ${POSTGRES_PASSWORD:admin} + driver-class-name: org.postgresql.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + default_schema: restaurant_service + format_sql: true + show-sql: true +--- + +## 실행 순서 + +### 1. 인프라 및 DB 준비 +```bash +# 1. 인프라 시작 (PostgreSQL, Redis 등) +cd infra && docker-compose -f docker-compose.infra.yml up -d + +# 2. 테스트 데이터 리셋 및 시드 투입 (필수) +docker exec -i db psql -U admin -d michelet_db < http-tests/sql/reset_test_data.sql +docker exec -i db psql -U admin -d michelet_db < http-tests/sql/seed_test_data.sql +``` + +### 2. 서비스 기동 +`start-local.sh`를 사용하거나 별도 터미널에서 순서대로 기동합니다. (Eureka → User → Restaurant → Timeslot → Waiting → Reservation → Gateway) +실행후 Eureka 대시보드(http://localhost:8761)에서 모든 서비스가 `UP` 상태인지 확인합니다. + +### 3. 테스트 실행 (IntelliJ HTTP Client) + +#### **Scenario 1: 소비자 예약 플로우 (분리 실행)** +1. **`scenario1a_setup.http`**: 회원가입부터 대기열 등록(STEP 6)까지 한 번에 실행. +2. **10~20초 대기**: 스케줄러가 대기 토큰을 `ACTIVE`로 바꿀 때까지 기다립니다. +3. **`scenario1b_reservation.http`**: + - **STEP 7**을 먼저 실행하여 `ACTIVE` 상태를 확인합니다. + - 활성화가 확인되면 나머지 단계(예약 생성 등)를 진행합니다. + +#### **Scenario 2: 체크인 플로우 (`scenario2_checkin.http`)** +- 시드 데이터의 고정 예약(`b2c3d4e5-...`)을 사용하여 체크인(`COMPLETED`)을 테스트합니다. +- `http-client.env.json`에 미리 설정된 ID를 사용하므로 바로 실행 가능합니다. + +#### **Scenario 3: 방문 이력 검증 (`scenario3_history_check.http`)** +- 시드 데이터의 고정 예약(`a1b2c3d4-...`)을 사용합니다. +- 이 데이터는 항상 `CONFIRMED` 상태를 유지하므로, 시나리오 2 실행 여부와 관계없이 항상 성공합니다. diff --git a/http-tests/http-client.env.json b/http-tests/http-client.env.json new file mode 100644 index 0000000..aea7d91 --- /dev/null +++ b/http-tests/http-client.env.json @@ -0,0 +1,18 @@ +{ + "local": { + "gateway": "http://localhost:19000", + "reservationServiceUrl": "http://localhost:19500", + "accessToken": "", + "userId": "ffa8b6c9-cc64-41e8-a1b0-2b0622489b78", + "restaurantId": "2589a05e-db8f-4e62-bb3d-22ce85b750a3", + "timeSlotId": "", + "slotStartTime": "", + "courseId": "7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b", + "courseUnitPrice": 80000, + "waitingId": "", + "waitingToken": "", + "reservationId": "b2c3d4e5-f6a7-8901-bcde-f01234567891", + "reservationIdForHistory": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "targetDate": "2026-06-14" + } +} diff --git a/http-tests/scenario1a_setup.http b/http-tests/scenario1a_setup.http new file mode 100644 index 0000000..f57caa4 --- /dev/null +++ b/http-tests/scenario1a_setup.http @@ -0,0 +1,129 @@ +### Scenario 1-A: Consumer Setup (SignUp -> Waiting) +### 사전 조건: 인프라 및 모든 서비스 기동 완료 +### 이 파일은 대기열 등록(STEP 6)까지만 수행합니다. 이후 대기 상태가 ACTIVE가 되면 Scenario 1-B를 실행하세요. + +### [STEP 1] 회원가입 +POST {{gateway}}/api/v1/users/signup +Content-Type: application/json + +{ + "loginId": "testuser01", + "password": "Test123!@#", + "name": "테스트유저", + "email": "testuser01@example.com", + "phone": "010-1234-5678", + "userRole": "USER" +} + +> {% + client.test("회원가입 성공", function() { + client.assert(response.status === 201, "Expected 201 Created but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.userId !== undefined, "userId 반환 확인"); + } + }); + if (response.body && response.body.data) { + client.global.set("userId", response.body.data.userId); + } +%} + +### + +### [STEP 2] 로그인 → accessToken 추출 +POST {{gateway}}/api/v1/auth/login +Content-Type: application/json + +{ + "loginId": "testuser01", + "password": "Test123!@#" +} + +> {% + client.test("로그인 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.accessToken !== undefined, "accessToken 반환 확인"); + } + }); + if (response.body && response.body.data) { + client.global.set("accessToken", response.body.data.accessToken); + } +%} + +### + +### [STEP 3] 레스토랑 목록 검색 +GET {{gateway}}/api/v1/restaurants?page=0&size=5 +Authorization: Bearer {{accessToken}} + +> {% + client.test("레스토랑 목록 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data && response.body.data.content) { + client.assert(response.body.data.content.length > 0, "레스토랑 1개 이상 존재 확인"); + } + }); + if (response.body && response.body.data && response.body.data.content && response.body.data.content.length > 0) { + var first = response.body.data.content[0]; + client.global.set("restaurantId", first.restaurantId); + } +%} + +### + +### [STEP 4] 레스토랑 상세 조회 (코스 목록 포함) +GET {{gateway}}/api/v1/restaurants/{{restaurantId}} +Authorization: Bearer {{accessToken}} + +> {% + client.test("레스토랑 상세 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK"); + client.assert(response.body.data.restaurantId !== undefined, "restaurantId 확인"); + }); +%} + +### + +### [STEP 5] 코스 목록 조회 → courseId, 단가 추출 +GET {{gateway}}/api/v1/restaurants/{{restaurantId}}/courses +Authorization: Bearer {{accessToken}} + +> {% + client.test("코스 목록 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.length > 0, "코스 1개 이상 존재 확인"); + } + }); + if (response.body && response.body.data && response.body.data.length > 0) { + var course = response.body.data[0]; + client.global.set("courseId", course.courseId); + client.global.set("courseUnitPrice", course.price); + } +%} + +### + +### [STEP 6] 대기열 등록 → waitingId, waitingToken 추출 +POST {{gateway}}/api/v1/waitings +Authorization: Bearer {{accessToken}} +Content-Type: application/json + +{ + "userId": "{{userId}}", + "restaurantId": "{{restaurantId}}" +} + +> {% + client.test("대기열 등록 성공", function() { + client.assert(response.status === 201, "Expected 201 Created but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.waitingId !== undefined, "waitingId 반환 확인"); + client.assert(response.body.data.token !== undefined, "token 반환 확인"); + } + }); + if (response.body && response.body.data) { + client.global.set("waitingId", response.body.data.waitingId); + client.global.set("waitingToken", response.body.data.token); + } +%} diff --git a/http-tests/scenario1b_reservation.http b/http-tests/scenario1b_reservation.http new file mode 100644 index 0000000..5350146 --- /dev/null +++ b/http-tests/scenario1b_reservation.http @@ -0,0 +1,124 @@ +### Scenario 1-B: Consumer Reservation (Waiting Check -> Reservation) +### 사전 조건: Scenario 1-A 실행 완료 (waitingToken 획득 상태) +### 이 파일은 대기 상태 확인부터 실제 예약 생성 및 조회까지 수행합니다. + +### [STEP 7] 대기 상태 확인 (WAIT 10 SECONDS IF STATUS IS 'WAITING') +### 주의: 스케줄러가 10초마다 WAITING → ACTIVE 전환함 +### 만약 status가 WAITING 이라면, 10초 후 이 단계를 다시 실행하세요. +GET {{gateway}}/api/v1/waitings/status?token={{waitingToken}} +Authorization: Bearer {{accessToken}} + +> {% + client.test("대기 상태 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data) { + var status = response.body.data.status; + if (status !== "ACTIVE") { + client.log("!!! 현재 상태: " + status + ". 아직 ACTIVE가 아닙니다. 10초 후 이 STEP을 다시 실행하세요. !!!"); + } else { + client.log(">>> 현재 상태: ACTIVE. 이제 다음 STEP(8번)으로 진행 가능합니다. <<<"); + } + } + }); +%} + +### + +### [STEP 8] 월별 예약 가능 달력 조회 (2026년 6월) +GET {{gateway}}/api/v1/restaurants/{{restaurantId}}/time-slots/calendar?year=2026&month=6 +Authorization: Bearer {{accessToken}} + +> {% + client.test("월별 달력 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK"); + }); + var slots = response.body.data; + var opened = slots.filter(function(s) { return s.status === "OPENED"; }); + if (opened.length > 0) { + client.global.set("targetDate", opened[0].date); + client.log("예약 가능 날짜: " + opened[0].date); + } +%} + +### + +### [STEP 9] 특정 날짜 타임슬롯 목록 조회 → timeSlotId, slotStartTime 추출 +GET {{gateway}}/api/v1/restaurants/{{restaurantId}}/time-slots?targetDate={{targetDate}} +Authorization: Bearer {{accessToken}} + +> {% + client.test("타임슬롯 목록 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK"); + client.assert(response.body.data.length > 0, "타임슬롯 1개 이상 존재 확인"); + }); + var available = response.body.data.filter(function(s) { + return s.remainingCapacity > 0; + }); + if (available.length > 0) { + client.global.set("timeSlotId", available[0].timeSlotId); + client.global.set("slotStartTime", available[0].startTime); + client.log("선택된 타임슬롯: " + available[0].timeSlotId + " (" + available[0].startTime + ")"); + } +%} + +### + +### [STEP 10] 예약 생성 +### 전제: STEP 7에서 대기 상태가 ACTIVE 인 것을 확인한 후 실행 +POST {{gateway}}/api/v1/reservations +Authorization: Bearer {{accessToken}} +X-Waiting-Token: {{waitingToken}} +Content-Type: application/json + +{ + "restaurantId": "{{restaurantId}}", + "timeSlotId": "{{timeSlotId}}", + "reservedDate": "{{targetDate}}", + "slotStartTime": "{{slotStartTime}}", + "guestCount": 2, + "courses": [ + { + "courseId": "{{courseId}}", + "quantity": 2, + "unitPrice": {{courseUnitPrice}} + } + ] +} + +> {% + client.test("예약 생성 성공", function() { + client.assert(response.status === 201, "Expected 201 Created but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.reservationId !== undefined, "reservationId 반환 확인"); + } + }); + if (response.body && response.body.data) { + client.global.set("reservationId", response.body.data.reservationId); + } +%} + +### + +### [STEP 11] 예약 상세 조회 +GET {{gateway}}/api/v1/reservations/{{reservationId}} +Authorization: Bearer {{accessToken}} + +> {% + client.test("예약 상세 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK"); + client.assert(response.body.data.status === "CONFIRMED", "예약 상태 CONFIRMED 확인"); + }); +%} + +### + +### [STEP 12] 내 예약 목록 조회 +GET {{gateway}}/api/v1/reservations?page=0&size=10 +Authorization: Bearer {{accessToken}} + +> {% + client.test("예약 목록 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK"); + client.assert(response.body.data.content.length > 0, "예약 1개 이상 존재 확인"); + }); +%} diff --git a/http-tests/scenario2_checkin.http b/http-tests/scenario2_checkin.http new file mode 100644 index 0000000..a3ac0ad --- /dev/null +++ b/http-tests/scenario2_checkin.http @@ -0,0 +1,44 @@ +### Scenario 2: Consumer Check-in Flow +### 사전 조건: Scenario 1 완료 (reservationId, userId, restaurantId가 env에 세팅된 상태) +### 내부 API 직접 호출 (gateway 미통과) - reservation-service 직접 접근 +### 실제 서비스 간 통신은 FeignClient(lb://)를 통해 Eureka 기반으로 이루어짐 +### + +### [STEP 1] 예약 존재 여부 확인 (Internal API) +### 특정 예약 ID + 유저 + 레스토랑 조합으로 존재 여부 검증 +GET {{reservationServiceUrl}}/internal/reservations/exists?reservationId={{reservationId}}&userId={{userId}}&restaurantId={{restaurantId}} + +> {% + client.test("예약 존재 여부 확인 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.exists === true, "예약 존재 확인 (exists=true)"); + } + }); +%} + +### + +### [STEP 2] 체크인 처리 (Internal API) +### 레스토랑 스태프가 고객 도착 시 호출 +PATCH {{reservationServiceUrl}}/internal/reservations/check-in +Content-Type: application/json + +{ + "reservationId": "{{reservationId}}", + "restaurantId": "{{restaurantId}}" +} + +> {% + client.test("체크인 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + if (response.body && response.body.data) { + client.assert(response.body.data.status === "COMPLETED", "예약 상태 COMPLETED 확인"); + client.assert(response.body.data.checkedInAt !== null, "체크인 시각 기록 확인"); + } + }); + if (response.body && response.body.data) { + client.log("체크인 완료 - 상태: " + response.body.data.status); + client.log("체크인 시각: " + response.body.data.checkedInAt); + } +%} diff --git a/http-tests/scenario3_history_check.http b/http-tests/scenario3_history_check.http new file mode 100644 index 0000000..6cbdef9 --- /dev/null +++ b/http-tests/scenario3_history_check.http @@ -0,0 +1,27 @@ +### Scenario 3: Reservation History Check (방문 이력 검증) +### 사전 조건: seed_test_data.sql 실행 완료 (고정 예약 데이터 존재) +### 이 테스트는 시드 데이터로 삽입된 고정 ID를 사용하여 검증합니다. +### 활용 사례: 레스토랑 전용 콘텐츠(리뷰, 특별 메뉴 등)를 과거 방문자에게만 공개할 때 +### 내부 API 직접 호출 - reservation-service 직접 접근 + +### [STEP 1] 방문 이력 검증 (Internal API) +### 특정 유저가 특정 레스토랑에 예약한 이력이 있는지 확인 +### CONFIRMED/COMPLETED 상태 예약이 존재하면 exists=true 반환 +GET {{reservationServiceUrl}}/internal/reservations/verify?userId={{userId}}&restaurantId={{restaurantId}} + +> {% + client.test("예약 이력 조회 성공", function() { + client.assert(response.status === 200, "Expected 200 OK but got " + response.status); + }); + if (response.body && response.body.data) { + var data = response.body.data; + client.log("예약 이력 존재: " + data.exists); + if (data.exists) { + client.log("예약 ID: " + data.reservationId); + client.log("예약 날짜: " + data.reservationDate); + client.log("→ 방문 이력 있음: 레스토랑 전용 콘텐츠 접근 허용"); + } else { + client.log("→ 방문 이력 없음: 레스토랑 전용 콘텐츠 접근 차단"); + } + } +%} diff --git a/http-tests/sql/reset_test_data.sql b/http-tests/sql/reset_test_data.sql new file mode 100644 index 0000000..e4447ed --- /dev/null +++ b/http-tests/sql/reset_test_data.sql @@ -0,0 +1,38 @@ +-- ============================================================ +-- Miche-Let 테스트 데이터 초기화 (완전 리셋) +-- 기존 시드 데이터를 삭제하고 seed_test_data.sql을 재실행합니다. +-- +-- 실행 방법: +-- docker exec -i db psql -U admin -d michelet_db \ +-- < http-tests/sql/reset_test_data.sql +-- docker exec -i db psql -U admin -d michelet_db \ +-- < http-tests/sql/seed_test_data.sql +-- +-- 주의: 이 파일은 시드 데이터만 삭제합니다. +-- 다른 유저/레스토랑 데이터가 있으면 함께 지워질 수 있습니다. +-- ============================================================ + +-- 외래 키 의존 순서대로 삭제 (자식 → 부모) +DELETE FROM timeslot_service.p_time_slot +WHERE restaurant_id = '2589a05e-db8f-4e62-bb3d-22ce85b750a3'; + +DELETE FROM restaurant_service.p_restaurant_course_menu +WHERE course_id = '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b'; + +DELETE FROM restaurant_service.p_restaurant_course +WHERE course_id = '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b'; + +DELETE FROM restaurant_service.p_restaurant +WHERE restaurant_id = '2589a05e-db8f-4e62-bb3d-22ce85b750a3'; + +DELETE FROM reservation_db.p_reservations +WHERE id IN ('a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'b2c3d4e5-f6a7-8901-bcde-f01234567891'); + +-- owneruser01 삭제 (seed에서 다시 삽입됨) +DELETE FROM user_service.p_users +WHERE id = 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78'; + +-- 시나리오 실행 중 생성된 테스트 유저 삭제 (signup으로 생성되므로 UUID 미확정 → login_id로 삭제) +DELETE FROM user_service.p_users WHERE login_id IN ('testuser01', 'testuser02'); + +SELECT '초기화 완료 — seed_test_data.sql을 실행하세요.' AS "상태"; diff --git a/http-tests/sql/seed_test_data.sql b/http-tests/sql/seed_test_data.sql new file mode 100644 index 0000000..df7d07f --- /dev/null +++ b/http-tests/sql/seed_test_data.sql @@ -0,0 +1,298 @@ +-- ============================================================ +-- Miche-Let 로컬 테스트 시드 데이터 +-- 대상 DB: michelet_db (PostgreSQL) +-- 사전 조건: 모든 서비스를 최소 1회 기동하여 Hibernate DDL로 테이블 생성 완료 +-- +-- 실행 방법: +-- docker exec -i db psql -U admin -d michelet_db \ +-- < http-tests/sql/seed_test_data.sql +-- +-- 멱등성: ON CONFLICT DO NOTHING 처리 — 중복 실행 안전 +-- ============================================================ + +-- BCrypt 해시 함수(crypt, gen_salt) 사용을 위해 pgcrypto 활성화 +CREATE EXTENSION IF NOT EXISTS pgcrypto; + +-- 시스템 생성자 UUID (시드 데이터용 더미 created_by) +-- created_by 컬럼이 NOT NULL이므로 시드 전용 UUID를 사용 +-- 실제 서비스에서는 AuditorConfig가 JWT에서 추출한 userId를 자동 주입함 + +-- ============================================================ +-- 1. 유저 (user_service.p_users) +-- 비밀번호: Test123!@# (BCrypt strength 10) +-- +-- ※ testuser01, testuser02는 HTTP 시나리오(signup 단계)가 직접 생성합니다. +-- 여기서 삽입하면 시나리오 STEP 1(회원가입)이 409 충돌로 실패합니다. +-- owneruser01만 사전 삽입합니다 (레스토랑의 owner_id 참조 필요). +-- ============================================================ +INSERT INTO user_service.p_users ( + id, + login_id, + password, + name, + email, + phone, + role, + status, + last_login_at, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) VALUES ( + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + 'owneruser01', + crypt('Test123!@#', gen_salt('bf', 10)), + '오너유저', + 'owneruser01@example.com', + '010-0000-0001', + 'OWNER', + 'ACTIVE', + NULL, + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', -- self-reference (seed 데이터) + NOW(), + NULL, + NULL, + NULL +) +ON CONFLICT (id) DO NOTHING; + +-- ============================================================ +-- 2. 레스토랑 (restaurant_service.p_restaurant) +-- ============================================================ +INSERT INTO restaurant_service.p_restaurant ( + restaurant_id, + owner_id, + name, + address, + phone, + description, + reservation_open_at, + avg_meal_duration_min, + status, + business_hours, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) VALUES ( + '2589a05e-db8f-4e62-bb3d-22ce85b750a3', + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + '테스트 레스토랑', + '서울시 강남구 테헤란로 123', + '02-1234-5678', + '로컬 통합 테스트용 레스토랑입니다.', + '09:00', + 60, + 'OPEN', + '화-일 18:00-22:00 (월요일 정기 휴무)', + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + NOW(), + NULL, + NULL, + NULL +) +ON CONFLICT (restaurant_id) DO NOTHING; + +-- ============================================================ +-- 3. 코스 (restaurant_service.p_restaurant_course) +-- ============================================================ +INSERT INTO restaurant_service.p_restaurant_course ( + course_id, + restaurant_id, + name, + price, + menu_composition, + session_type, + status, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) VALUES ( + '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b', + '2589a05e-db8f-4e62-bb3d-22ce85b750a3', + '프리미엄 코스', + 80000, + '전채 → 생선 요리 → 메인 → 디저트', + 'DINNER', + 'AVAILABLE', + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + NOW(), + NULL, + NULL, + NULL +) +ON CONFLICT (course_id) DO NOTHING; + +-- ============================================================ +-- 4. 코스 메뉴 (restaurant_service.p_restaurant_course_menu) +-- (course_id, sort_order) 유니크 제약으로 중복 방지 +-- ============================================================ +INSERT INTO restaurant_service.p_restaurant_course_menu ( + course_menu_id, + course_id, + course_part, + menu_name, + sort_order, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) VALUES +( + gen_random_uuid(), + '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b', + 'APPETIZER', '모듬 샐러드', 1, + NOW(), 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', NOW(), NULL, NULL, NULL +), +( + gen_random_uuid(), + '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b', + 'FISH', '연어 카르파쵸', 2, + NOW(), 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', NOW(), NULL, NULL, NULL +), +( + gen_random_uuid(), + '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b', + 'MAIN', '안심 스테이크', 3, + NOW(), 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', NOW(), NULL, NULL, NULL +), +( + gen_random_uuid(), + '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b', + 'DESSERT', '티라미수', 4, + NOW(), 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', NOW(), NULL, NULL, NULL +) +ON CONFLICT ON CONSTRAINT uk_restaurant_course_menu_course_id_sort_order DO NOTHING; + +-- ============================================================ +-- 5. 타임슬롯 (timeslot_service.p_time_slot) — 2026년 6월 전체 +-- 하루 4슬롯: 18:00 / 19:00 / 20:00 / 21:00 (1시간 단위) +-- 수용 10명, OPENED, version=0 +-- (restaurant_id, target_date, start_time) 유니크 제약으로 중복 방지 +-- ============================================================ +INSERT INTO timeslot_service.p_time_slot ( + time_slot_id, + restaurant_id, + target_date, + start_time, + end_time, + capacity, + remaining_capacity, + status, + version, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) +SELECT + gen_random_uuid(), + '2589a05e-db8f-4e62-bb3d-22ce85b750a3'::UUID, + d::DATE, + t::TIME, + (t::TIME + INTERVAL '1 hour'), + 10, + 10, + 'OPENED', + 0, + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78'::UUID, + NOW(), + NULL, + NULL, + NULL +FROM + generate_series('2026-06-01'::DATE, '2026-06-30'::DATE, '1 day') AS d, + (VALUES ('18:00'), ('19:00'), ('20:00'), ('21:00')) AS times(t) +ON CONFLICT ON CONSTRAINT uk_time_slot_restaurant_date_start DO NOTHING; + +-- ============================================================ +-- 6. 예약 (reservation_db.p_reservations) — 시나리오 3 검증용 CONFIRMED 데이터 +-- owneruser01이 본인 식당에 예약한 데이터로 생성 (임시) +-- ============================================================ +INSERT INTO reservation_db.p_reservations ( + id, + user_id, + restaurant_id, + time_slot_id, + reserved_date, + guest_count, + status, + cancel_deadline, + modify_deadline, + noshow_deadline, + checked_in_at, + created_at, + created_by, + updated_at, + updated_by, + deleted_at, + deleted_by +) VALUES +( + 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', -- 시나리오 3 전용 (CONFIRMED 유지) + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', -- owneruser01 + '2589a05e-db8f-4e62-bb3d-22ce85b750a3', -- 테스트 레스토랑 + (SELECT time_slot_id FROM timeslot_service.p_time_slot WHERE target_date = '2026-06-14' AND start_time = '18:00' LIMIT 1), + '2026-06-14', + 2, + 'CONFIRMED', + '2026-06-12', + '2026-06-12', + '2026-06-14 18:30:00', + NULL, + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + NOW(), + NULL, + NULL, + NULL +), +( + 'b2c3d4e5-f6a7-8901-bcde-f01234567891', -- 시나리오 2 전용 (체크인 테스트용) + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + '2589a05e-db8f-4e62-bb3d-22ce85b750a3', + (SELECT time_slot_id FROM timeslot_service.p_time_slot WHERE target_date = '2026-06-14' AND start_time = '19:00' LIMIT 1), + '2026-06-14', + 4, + 'CONFIRMED', + '2026-06-12', + '2026-06-12', + '2026-06-14 19:30:00', + NULL, + NOW(), + 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78', + NOW(), + NULL, + NULL, + NULL +) +ON CONFLICT (id) DO NOTHING; + +-- 결과 확인 +SELECT 'p_users' AS "테이블", COUNT(*) AS "행 수" FROM user_service.p_users +UNION ALL +SELECT 'p_restaurant', COUNT(*) FROM restaurant_service.p_restaurant +UNION ALL +SELECT 'p_restaurant_course', COUNT(*) FROM restaurant_service.p_restaurant_course +UNION ALL +SELECT 'p_restaurant_course_menu', COUNT(*) FROM restaurant_service.p_restaurant_course_menu +UNION ALL +SELECT 'p_time_slot', COUNT(*) FROM timeslot_service.p_time_slot +UNION ALL +SELECT 'p_reservations', COUNT(*) FROM reservation_db.p_reservations; diff --git a/http-tests/start-local.sh b/http-tests/start-local.sh new file mode 100755 index 0000000..c36a921 --- /dev/null +++ b/http-tests/start-local.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Miche-Let 로컬 서비스 기동 스크립트 +# 사용법: ./start-local.sh [서비스명] +# 예시: ./start-local.sh user-service +# ./start-local.sh all (모든 서비스 순서대로 기동) + +set -e + +# infra .env 로드 +ENV_FILE="$(dirname "$0")/infra/.env" +if [ -f "$ENV_FILE" ]; then + export $(grep -v '^#' "$ENV_FILE" | xargs) +fi + +ROOT="$(dirname "$0")" + +run_service() { + local svc=$1 + local profile=${2:-local} + local extra_args=${3:-} + + echo "▶ Starting $svc (profile=$profile)..." + cd "$ROOT/$svc" + chmod +x gradlew + + if [ -n "$extra_args" ]; then + ./gradlew bootRun --args="--spring.profiles.active=$profile $extra_args" & + else + ./gradlew bootRun --args="--spring.profiles.active=$profile" & + fi + cd "$ROOT" +} + +case "${1:-}" in + user-service) + run_service user-service local + ;; + restaurant-service) + run_service restaurant-service local + ;; + timeslot-service) + run_service timeslot-service + ;; + waiting-service) + run_service waiting-service + ;; + reservation-service) + run_service reservation-service local + ;; + api-gateway) + run_service api-gateway local + ;; + all) + echo "=== 전체 서비스 기동 (Eureka + Docker 인프라가 이미 실행 중이어야 합니다) ===" + run_service user-service local + sleep 5 + run_service restaurant-service local + run_service timeslot-service + run_service waiting-service + sleep 5 + run_service reservation-service local + sleep 5 + run_service api-gateway local + echo "=== 모든 서비스 기동 완료. Eureka 확인: http://localhost:8761 ===" + wait + ;; + *) + echo "사용법: $0 [user-service|restaurant-service|timeslot-service|waiting-service|reservation-service|api-gateway|all]" + echo "" + echo "각 터미널에서 개별 실행:" + echo " source infra/.env && cd user-service && ./gradlew bootRun --args='--spring.profiles.active=local'" + echo " source infra/.env && cd restaurant-service && ./gradlew bootRun --args='--spring.profiles.active=local'" + echo " source infra/.env && cd timeslot-service && ./gradlew bootRun" + echo " source infra/.env && cd waiting-service && ./gradlew bootRun" + echo " source infra/.env && cd reservation-service && ./gradlew bootRun --args='--spring.profiles.active=local'" + echo " source infra/.env && cd api-gateway && ./gradlew bootRun --args='--spring.profiles.active=local'" + ;; +esac From 28359e675f56b7cc3f74a105487655ce4d18b195 Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Mon, 4 May 2026 23:50:13 +0900 Subject: [PATCH 6/7] chore: update local HTTP client test guide with additional scenarios and clarifications --- http-tests/LOCAL_TEST_GUIDE.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/http-tests/LOCAL_TEST_GUIDE.md b/http-tests/LOCAL_TEST_GUIDE.md index 4ac5953..3e82d92 100644 --- a/http-tests/LOCAL_TEST_GUIDE.md +++ b/http-tests/LOCAL_TEST_GUIDE.md @@ -3,6 +3,8 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. 모든 서비스가 Eureka에 등록되고, FeignClient는 `lb://` 로드밸런싱으로 내부 서비스를 호출합니다. +단, 이는 예약 생성 시나리오, 타 시나리오상 예약 서비스가 활용되는 내부 api 만한정하여 작성함. + --- ## 환경 설정 @@ -10,12 +12,15 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. ### 서비스 설정 및 Java 코드 변경 # waiting-service + application.yml - Eureka 클라이언트 설정 추가 (서비스 등록 및 검색) - port 19200 build.gradle - Spring Cloud Netflix Eureka Client 의존성 추가 + # user-service + application.yml 설정 eureka: @@ -42,8 +47,9 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. default_schema: user_service format_sql: true show-sql: true - + # timeslot-service + application.yml 설정 eureka: @@ -66,7 +72,9 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. default_schema: timeslot_service format_sql: true show-sql: true + # restaurant-service + application.yml 설정 eureka: @@ -89,11 +97,13 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. default_schema: restaurant_service format_sql: true show-sql: true + --- ## 실행 순서 ### 1. 인프라 및 DB 준비 + ```bash # 1. 인프라 시작 (PostgreSQL, Redis 등) cd infra && docker-compose -f docker-compose.infra.yml up -d @@ -104,22 +114,26 @@ docker exec -i db psql -U admin -d michelet_db < http-tests/sql/seed_test_data.s ``` ### 2. 서비스 기동 + `start-local.sh`를 사용하거나 별도 터미널에서 순서대로 기동합니다. (Eureka → User → Restaurant → Timeslot → Waiting → Reservation → Gateway) 실행후 Eureka 대시보드(http://localhost:8761)에서 모든 서비스가 `UP` 상태인지 확인합니다. ### 3. 테스트 실행 (IntelliJ HTTP Client) #### **Scenario 1: 소비자 예약 플로우 (분리 실행)** -1. **`scenario1a_setup.http`**: 회원가입부터 대기열 등록(STEP 6)까지 한 번에 실행. -2. **10~20초 대기**: 스케줄러가 대기 토큰을 `ACTIVE`로 바꿀 때까지 기다립니다. -3. **`scenario1b_reservation.http`**: + +1. **`scenario1a_setup.http`**: 회원가입부터 대기열 등록(STEP 6)까지 한 번에 실행. +2. **10~20초 대기**: 스케줄러가 대기 토큰을 `ACTIVE`로 바꿀 때까지 기다립니다. +3. **`scenario1b_reservation.http`**: - **STEP 7**을 먼저 실행하여 `ACTIVE` 상태를 확인합니다. - 활성화가 확인되면 나머지 단계(예약 생성 등)를 진행합니다. #### **Scenario 2: 체크인 플로우 (`scenario2_checkin.http`)** + - 시드 데이터의 고정 예약(`b2c3d4e5-...`)을 사용하여 체크인(`COMPLETED`)을 테스트합니다. - `http-client.env.json`에 미리 설정된 ID를 사용하므로 바로 실행 가능합니다. #### **Scenario 3: 방문 이력 검증 (`scenario3_history_check.http`)** + - 시드 데이터의 고정 예약(`a1b2c3d4-...`)을 사용합니다. - 이 데이터는 항상 `CONFIRMED` 상태를 유지하므로, 시나리오 2 실행 여부와 관계없이 항상 성공합니다. From e68d18bc352cb9ed94fb4745bf163503a4499d2b Mon Sep 17 00:00:00 2001 From: githyj-jang Date: Tue, 5 May 2026 22:08:39 +0900 Subject: [PATCH 7/7] chore: update configuration files and improve local test setup --- build.gradle | 4 ++-- http-tests/LOCAL_TEST_GUIDE.md | 4 ++-- http-tests/scenario1b_reservation.http | 24 ++++++++++++----------- http-tests/sql/reset_test_data.sql | 6 +++--- http-tests/sql/seed_test_data.sql | 6 +++--- http-tests/start-local.sh | 14 ++++++++----- src/main/resources/application-local.yaml | 8 ++++++++ src/main/resources/application.yaml | 6 +++--- 8 files changed, 43 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index 56a8c9b..70d7bf1 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ version = '0.0.1-SNAPSHOT' java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(17) } } @@ -25,7 +25,7 @@ ext { dependencies { implementation 'com.github.Miche-Let:common:dev-SNAPSHOT' implementation 'com.github.Miche-Let:common-auth-webmvc:0.1.5' - implementation 'com.github.Miche-Let:common-auth-feign:0.1.1' + implementation 'com.github.Miche-Let:common-auth-feign:0.1.2' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-validation' diff --git a/http-tests/LOCAL_TEST_GUIDE.md b/http-tests/LOCAL_TEST_GUIDE.md index 3e82d92..d347896 100644 --- a/http-tests/LOCAL_TEST_GUIDE.md +++ b/http-tests/LOCAL_TEST_GUIDE.md @@ -59,7 +59,7 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. spring: datasource: - url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432/${POSTGRES_DB:michelet_db} + url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432}/${POSTGRES_DB:michelet_db} username: ${POSTGRES_USER:admin} password: ${POSTGRES_PASSWORD:admin} driver-class-name: org.postgresql.Driver @@ -84,7 +84,7 @@ IntelliJ HTTP Client 기반 로컬 통합 테스트 가이드. spring: datasource: - url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432/${POSTGRES_DB:michelet_db} + url: jdbc:postgresql://localhost:${POSTGRES_PORT:5432}/${POSTGRES_DB:michelet_db} username: ${POSTGRES_USER:admin} password: ${POSTGRES_PASSWORD:admin} driver-class-name: org.postgresql.Driver diff --git a/http-tests/scenario1b_reservation.http b/http-tests/scenario1b_reservation.http index 5350146..e410d60 100644 --- a/http-tests/scenario1b_reservation.http +++ b/http-tests/scenario1b_reservation.http @@ -11,14 +11,9 @@ Authorization: Bearer {{accessToken}} > {% client.test("대기 상태 조회 성공", function() { client.assert(response.status === 200, "Expected 200 OK but got " + response.status); - if (response.body && response.body.data) { - var status = response.body.data.status; - if (status !== "ACTIVE") { - client.log("!!! 현재 상태: " + status + ". 아직 ACTIVE가 아닙니다. 10초 후 이 STEP을 다시 실행하세요. !!!"); - } else { - client.log(">>> 현재 상태: ACTIVE. 이제 다음 STEP(8번)으로 진행 가능합니다. <<<"); - } - } + client.assert(response.body && response.body.data, "응답 본문(data)이 없습니다."); + var status = response.body.data.status; + client.assert(status === "ACTIVE", "아직 ACTIVE가 아닙니다 (현재: " + status + "). 10초 후 이 STEP을 다시 실행하세요."); }); %} @@ -32,8 +27,13 @@ Authorization: Bearer {{accessToken}} client.test("월별 달력 조회 성공", function() { client.assert(response.status === 200, "Expected 200 OK"); }); + client.test("예약 가능 날짜 존재 확인", function() { + var slots = response.body.data; + var opened = slots ? slots.filter(function(s) { return s.status === "OPENED"; }) : []; + client.assert(opened.length > 0, "OPENED 상태의 날짜가 없습니다. 시드 데이터를 확인하세요."); + }); var slots = response.body.data; - var opened = slots.filter(function(s) { return s.status === "OPENED"; }); + var opened = slots ? slots.filter(function(s) { return s.status === "OPENED"; }) : []; if (opened.length > 0) { client.global.set("targetDate", opened[0].date); client.log("예약 가능 날짜: " + opened[0].date); @@ -51,9 +51,11 @@ Authorization: Bearer {{accessToken}} client.assert(response.status === 200, "Expected 200 OK"); client.assert(response.body.data.length > 0, "타임슬롯 1개 이상 존재 확인"); }); - var available = response.body.data.filter(function(s) { - return s.remainingCapacity > 0; + client.test("예약 가능 슬롯 존재 확인", function() { + var available = response.body.data ? response.body.data.filter(function(s) { return s.remainingCapacity > 0; }) : []; + client.assert(available.length > 0, "잔여 용량이 있는 타임슬롯이 없습니다. 시드 데이터를 확인하세요."); }); + var available = response.body.data ? response.body.data.filter(function(s) { return s.remainingCapacity > 0; }) : []; if (available.length > 0) { client.global.set("timeSlotId", available[0].timeSlotId); client.global.set("slotStartTime", available[0].startTime); diff --git a/http-tests/sql/reset_test_data.sql b/http-tests/sql/reset_test_data.sql index e4447ed..805bade 100644 --- a/http-tests/sql/reset_test_data.sql +++ b/http-tests/sql/reset_test_data.sql @@ -13,6 +13,9 @@ -- ============================================================ -- 외래 키 의존 순서대로 삭제 (자식 → 부모) +DELETE FROM reservation_service.p_reservations +WHERE id IN ('a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'b2c3d4e5-f6a7-8901-bcde-f01234567891'); + DELETE FROM timeslot_service.p_time_slot WHERE restaurant_id = '2589a05e-db8f-4e62-bb3d-22ce85b750a3'; @@ -25,9 +28,6 @@ WHERE course_id = '7d376f47-8fb3-4be1-916f-4ee0e6ee8b3b'; DELETE FROM restaurant_service.p_restaurant WHERE restaurant_id = '2589a05e-db8f-4e62-bb3d-22ce85b750a3'; -DELETE FROM reservation_db.p_reservations -WHERE id IN ('a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'b2c3d4e5-f6a7-8901-bcde-f01234567891'); - -- owneruser01 삭제 (seed에서 다시 삽입됨) DELETE FROM user_service.p_users WHERE id = 'ffa8b6c9-cc64-41e8-a1b0-2b0622489b78'; diff --git a/http-tests/sql/seed_test_data.sql b/http-tests/sql/seed_test_data.sql index df7d07f..256207d 100644 --- a/http-tests/sql/seed_test_data.sql +++ b/http-tests/sql/seed_test_data.sql @@ -222,10 +222,10 @@ FROM ON CONFLICT ON CONSTRAINT uk_time_slot_restaurant_date_start DO NOTHING; -- ============================================================ --- 6. 예약 (reservation_db.p_reservations) — 시나리오 3 검증용 CONFIRMED 데이터 +-- 6. 예약 (reservation_service.p_reservations) — 시나리오 3 검증용 CONFIRMED 데이터 -- owneruser01이 본인 식당에 예약한 데이터로 생성 (임시) -- ============================================================ -INSERT INTO reservation_db.p_reservations ( +INSERT INTO reservation_service.p_reservations ( id, user_id, restaurant_id, @@ -295,4 +295,4 @@ SELECT 'p_restaurant_course_menu', COUNT(*) FROM restaurant_service.p_restauran UNION ALL SELECT 'p_time_slot', COUNT(*) FROM timeslot_service.p_time_slot UNION ALL -SELECT 'p_reservations', COUNT(*) FROM reservation_db.p_reservations; +SELECT 'p_reservations', COUNT(*) FROM reservation_service.p_reservations; diff --git a/http-tests/start-local.sh b/http-tests/start-local.sh index c36a921..b05de58 100755 --- a/http-tests/start-local.sh +++ b/http-tests/start-local.sh @@ -6,14 +6,18 @@ set -e -# infra .env 로드 -ENV_FILE="$(dirname "$0")/infra/.env" +# 스크립트 위치 기준으로 miche-let 루트 디렉터리 산출 (http-tests/../.. = miche-let/) +ROOT="$(cd "$(dirname "$0")/../.." && pwd)" + +# infra .env 로드 (값에 공백이 포함될 수 있으므로 set -a 방식 사용) +ENV_FILE="$ROOT/infra/.env" if [ -f "$ENV_FILE" ]; then - export $(grep -v '^#' "$ENV_FILE" | xargs) + set -a + # shellcheck disable=SC1090 + source "$ENV_FILE" + set +a fi -ROOT="$(dirname "$0")" - run_service() { local svc=$1 local profile=${2:-local} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index 27b6a53..119a773 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,6 +1,14 @@ spring: kafka: bootstrap-servers: localhost:9092 + jpa: + hibernate: + ddl-auto: create + +internal: + secret: ${INTERNAL_AUTH_SECRET} + auth: + secret: ${INTERNAL_AUTH_SECRET} waiting: stub: false diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d704dcf..8215bb6 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -10,17 +10,17 @@ spring: jpa: hibernate: - ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO:create} + ddl-auto: ${SPRING_JPA_HIBERNATE_DDL_AUTO:validate} properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect default_schema: reservation_service format_sql: true jdbc.time_zone: Asia/Seoul + order_inserts: true + order_updates: true jdbc: batch_size: 30 - order_inserts: true - order_updates: true show-sql: false open-in-view: false