From 9afebf59b3bfd17e9a24d0ff613e56cfdd96960e Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 26 Oct 2025 23:40:12 +0900
Subject: [PATCH 01/40] =?UTF-8?q?refactor:=20=ED=8C=A8=ED=82=A4=EC=A7=80?=
 =?UTF-8?q?=EB=AA=85,=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EB=AA=85?=
 =?UTF-8?q?=EC=8B=9C=EC=A0=81=20=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

`Example` -> `User` 로 리팩토링
---
 .../application/example/ExampleFacade.java    | 17 --------
 .../application/example/ExampleInfo.java      | 13 ------
 .../loopers/application/user/UserFacade.java  | 17 ++++++++
 .../loopers/application/user/UserInfo.java    | 13 ++++++
 .../domain/example/ExampleRepository.java     |  7 ----
 .../ExampleModel.java => user/UserModel.java} |  8 ++--
 .../loopers/domain/user/UserRepository.java   |  7 ++++
 .../UserService.java}                         | 10 ++---
 .../example/ExampleJpaRepository.java         |  6 ---
 .../example/ExampleRepositoryImpl.java        | 19 ---------
 .../user/UserJpaRepository.java               |  6 +++
 .../user/UserRepositoryImpl.java              | 19 +++++++++
 .../UserV1ApiSpec.java}                       |  6 +--
 .../UserV1Controller.java}                    | 16 ++++----
 .../ExampleV1Dto.java => user/UserV1Dto.java} |  8 ++--
 .../UserModelTest.java}                       | 16 ++++----
 .../UserServiceIntegrationTest.java}          | 22 +++++-----
 ...1ApiE2ETest.java => UserV1ApiE2ETest.java} | 40 +++++++++----------
 http/commerce-api/user-v1.http                |  2 +
 19 files changed, 127 insertions(+), 125 deletions(-)
 delete mode 100644 apps/commerce-api/src/main/java/com/loopers/application/example/ExampleFacade.java
 delete mode 100644 apps/commerce-api/src/main/java/com/loopers/application/example/ExampleInfo.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
 delete mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleRepository.java
 rename apps/commerce-api/src/main/java/com/loopers/domain/{example/ExampleModel.java => user/UserModel.java} (86%)
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
 rename apps/commerce-api/src/main/java/com/loopers/domain/{example/ExampleService.java => user/UserService.java} (68%)
 delete mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleJpaRepository.java
 delete mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleRepositoryImpl.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
 rename apps/commerce-api/src/main/java/com/loopers/interfaces/api/{example/ExampleV1ApiSpec.java => user/UserV1ApiSpec.java} (78%)
 rename apps/commerce-api/src/main/java/com/loopers/interfaces/api/{example/ExampleV1Controller.java => user/UserV1Controller.java} (54%)
 rename apps/commerce-api/src/main/java/com/loopers/interfaces/api/{example/ExampleV1Dto.java => user/UserV1Dto.java} (56%)
 rename apps/commerce-api/src/test/java/com/loopers/domain/{example/ExampleModelTest.java => user/UserModelTest.java} (78%)
 rename apps/commerce-api/src/test/java/com/loopers/domain/{example/ExampleServiceIntegrationTest.java => user/UserServiceIntegrationTest.java} (73%)
 rename apps/commerce-api/src/test/java/com/loopers/interfaces/api/{ExampleV1ApiE2ETest.java => UserV1ApiE2ETest.java} (71%)
 create mode 100644 http/commerce-api/user-v1.http

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleFacade.java
deleted file mode 100644
index 552a9ad62..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleFacade.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.loopers.application.example;
-
-import com.loopers.domain.example.ExampleModel;
-import com.loopers.domain.example.ExampleService;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Component;
-
-@RequiredArgsConstructor
-@Component
-public class ExampleFacade {
-    private final ExampleService exampleService;
-
-    public ExampleInfo getExample(Long id) {
-        ExampleModel example = exampleService.getExample(id);
-        return ExampleInfo.from(example);
-    }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleInfo.java
deleted file mode 100644
index 877aba96c..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/application/example/ExampleInfo.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.loopers.application.example;
-
-import com.loopers.domain.example.ExampleModel;
-
-public record ExampleInfo(Long id, String name, String description) {
-    public static ExampleInfo from(ExampleModel model) {
-        return new ExampleInfo(
-            model.getId(),
-            model.getName(),
-            model.getDescription()
-        );
-    }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
new file mode 100644
index 000000000..2d8e0f1b8
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -0,0 +1,17 @@
+package com.loopers.application.user;
+
+import com.loopers.domain.user.UserModel;
+import com.loopers.domain.user.UserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Component
+public class UserFacade {
+    private final UserService exampleService;
+
+    public UserInfo getExample(Long id) {
+        UserModel example = exampleService.getExample(id);
+        return UserInfo.from(example);
+    }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
new file mode 100644
index 000000000..3ce78ca5a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
@@ -0,0 +1,13 @@
+package com.loopers.application.user;
+
+import com.loopers.domain.user.UserModel;
+
+public record UserInfo(Long id, String name, String description) {
+    public static UserInfo from(UserModel model) {
+        return new UserInfo(
+            model.getId(),
+            model.getName(),
+            model.getDescription()
+        );
+    }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleRepository.java
deleted file mode 100644
index 3625e5662..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleRepository.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.loopers.domain.example;
-
-import java.util.Optional;
-
-public interface ExampleRepository {
-    Optional<ExampleModel> find(Long id);
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
similarity index 86%
rename from apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleModel.java
rename to apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index c588c4a8a..2b5f6b438 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -1,4 +1,4 @@
-package com.loopers.domain.example;
+package com.loopers.domain.user;
 
 import com.loopers.domain.BaseEntity;
 import com.loopers.support.error.CoreException;
@@ -8,14 +8,14 @@
 
 @Entity
 @Table(name = "example")
-public class ExampleModel extends BaseEntity {
+public class UserModel extends BaseEntity {
 
     private String name;
     private String description;
 
-    protected ExampleModel() {}
+    protected UserModel() {}
 
-    public ExampleModel(String name, String description) {
+    public UserModel(String name, String description) {
         if (name == null || name.isBlank()) {
             throw new CoreException(ErrorType.BAD_REQUEST, "이름은 비어있을 수 없습니다.");
         }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
new file mode 100644
index 000000000..a9400949b
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -0,0 +1,7 @@
+package com.loopers.domain.user;
+
+import java.util.Optional;
+
+public interface UserRepository {
+    Optional<UserModel> find(Long id);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
similarity index 68%
rename from apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleService.java
rename to apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index c0e8431e8..e6aab9a6d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/example/ExampleService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -1,4 +1,4 @@
-package com.loopers.domain.example;
+package com.loopers.domain.user;
 
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
@@ -8,13 +8,13 @@
 
 @RequiredArgsConstructor
 @Component
-public class ExampleService {
+public class UserService {
 
-    private final ExampleRepository exampleRepository;
+    private final UserRepository userRepository;
 
     @Transactional(readOnly = true)
-    public ExampleModel getExample(Long id) {
-        return exampleRepository.find(id)
+    public UserModel getExample(Long id) {
+        return userRepository.find(id)
             .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[id = " + id + "] 예시를 찾을 수 없습니다."));
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleJpaRepository.java
deleted file mode 100644
index ce6d3ead0..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleJpaRepository.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.loopers.infrastructure.example;
-
-import com.loopers.domain.example.ExampleModel;
-import org.springframework.data.jpa.repository.JpaRepository;
-
-public interface ExampleJpaRepository extends JpaRepository<ExampleModel, Long> {}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleRepositoryImpl.java
deleted file mode 100644
index 37f2272f0..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/example/ExampleRepositoryImpl.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.loopers.infrastructure.example;
-
-import com.loopers.domain.example.ExampleModel;
-import com.loopers.domain.example.ExampleRepository;
-import lombok.RequiredArgsConstructor;
-import org.springframework.stereotype.Component;
-
-import java.util.Optional;
-
-@RequiredArgsConstructor
-@Component
-public class ExampleRepositoryImpl implements ExampleRepository {
-    private final ExampleJpaRepository exampleJpaRepository;
-
-    @Override
-    public Optional<ExampleModel> find(Long id) {
-        return exampleJpaRepository.findById(id);
-    }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
new file mode 100644
index 000000000..7fd328068
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
@@ -0,0 +1,6 @@
+package com.loopers.infrastructure.user;
+
+import com.loopers.domain.user.UserModel;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserJpaRepository extends JpaRepository<UserModel, Long> {}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
new file mode 100644
index 000000000..499841f39
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -0,0 +1,19 @@
+package com.loopers.infrastructure.user;
+
+import com.loopers.domain.user.UserModel;
+import com.loopers.domain.user.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+@RequiredArgsConstructor
+@Component
+public class UserRepositoryImpl implements UserRepository {
+    private final UserJpaRepository userJpaRepository;
+
+    @Override
+    public Optional<UserModel> find(Long id) {
+        return userJpaRepository.findById(id);
+    }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
similarity index 78%
rename from apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1ApiSpec.java
rename to apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 219e3101e..dfa383f1e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -1,4 +1,4 @@
-package com.loopers.interfaces.api.example;
+package com.loopers.interfaces.api.user;
 
 import com.loopers.interfaces.api.ApiResponse;
 import io.swagger.v3.oas.annotations.Operation;
@@ -6,13 +6,13 @@
 import io.swagger.v3.oas.annotations.tags.Tag;
 
 @Tag(name = "Example V1 API", description = "Loopers 예시 API 입니다.")
-public interface ExampleV1ApiSpec {
+public interface UserV1ApiSpec {
 
     @Operation(
         summary = "예시 조회",
         description = "ID로 예시를 조회합니다."
     )
-    ApiResponse<ExampleV1Dto.ExampleResponse> getExample(
+    ApiResponse<UserV1Dto.ExampleResponse> getExample(
         @Schema(name = "예시 ID", description = "조회할 예시의 ID")
         Long exampleId
     );
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
similarity index 54%
rename from apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Controller.java
rename to apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index 917376016..fa5d07d6f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -1,7 +1,7 @@
-package com.loopers.interfaces.api.example;
+package com.loopers.interfaces.api.user;
 
-import com.loopers.application.example.ExampleFacade;
-import com.loopers.application.example.ExampleInfo;
+import com.loopers.application.user.UserFacade;
+import com.loopers.application.user.UserInfo;
 import com.loopers.interfaces.api.ApiResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -12,17 +12,17 @@
 @RequiredArgsConstructor
 @RestController
 @RequestMapping("/api/v1/examples")
-public class ExampleV1Controller implements ExampleV1ApiSpec {
+public class UserV1Controller implements UserV1ApiSpec {
 
-    private final ExampleFacade exampleFacade;
+    private final UserFacade userFacade;
 
     @GetMapping("/{exampleId}")
     @Override
-    public ApiResponse<ExampleV1Dto.ExampleResponse> getExample(
+    public ApiResponse<UserV1Dto.ExampleResponse> getExample(
         @PathVariable(value = "exampleId") Long exampleId
     ) {
-        ExampleInfo info = exampleFacade.getExample(exampleId);
-        ExampleV1Dto.ExampleResponse response = ExampleV1Dto.ExampleResponse.from(info);
+        UserInfo info = userFacade.getExample(exampleId);
+        UserV1Dto.ExampleResponse response = UserV1Dto.ExampleResponse.from(info);
         return ApiResponse.success(response);
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
similarity index 56%
rename from apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Dto.java
rename to apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
index 4ecf0eea5..f0899a8bd 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/example/ExampleV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
@@ -1,10 +1,10 @@
-package com.loopers.interfaces.api.example;
+package com.loopers.interfaces.api.user;
 
-import com.loopers.application.example.ExampleInfo;
+import com.loopers.application.user.UserInfo;
 
-public class ExampleV1Dto {
+public class UserV1Dto {
     public record ExampleResponse(Long id, String name, String description) {
-        public static ExampleResponse from(ExampleInfo info) {
+        public static ExampleResponse from(UserInfo info) {
             return new ExampleResponse(
                 info.id(),
                 info.name(),
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
similarity index 78%
rename from apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleModelTest.java
rename to apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index 44ca7576e..d094bc0e8 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -1,4 +1,4 @@
-package com.loopers.domain.example;
+package com.loopers.domain.user;
 
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
@@ -10,7 +10,7 @@
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-class ExampleModelTest {
+class UserModelTest {
     @DisplayName("예시 모델을 생성할 때, ")
     @Nested
     class Create {
@@ -22,13 +22,13 @@ void createsExampleModel_whenNameAndDescriptionAreProvided() {
             String description = "설명";
 
             // act
-            ExampleModel exampleModel = new ExampleModel(name, description);
+            UserModel userModel = new UserModel(name, description);
 
             // assert
             assertAll(
-                () -> assertThat(exampleModel.getId()).isNotNull(),
-                () -> assertThat(exampleModel.getName()).isEqualTo(name),
-                () -> assertThat(exampleModel.getDescription()).isEqualTo(description)
+                () -> assertThat(userModel.getId()).isNotNull(),
+                () -> assertThat(userModel.getName()).isEqualTo(name),
+                () -> assertThat(userModel.getDescription()).isEqualTo(description)
             );
         }
 
@@ -40,7 +40,7 @@ void throwsBadRequestException_whenTitleIsBlank() {
 
             // act
             CoreException result = assertThrows(CoreException.class, () -> {
-                new ExampleModel(name, "설명");
+                new UserModel(name, "설명");
             });
 
             // assert
@@ -55,7 +55,7 @@ void throwsBadRequestException_whenDescriptionIsEmpty() {
 
             // act
             CoreException result = assertThrows(CoreException.class, () -> {
-                new ExampleModel("제목", description);
+                new UserModel("제목", description);
             });
 
             // assert
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
similarity index 73%
rename from apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleServiceIntegrationTest.java
rename to apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index bbd5fdbe1..fb7ed1732 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/example/ExampleServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -1,6 +1,6 @@
-package com.loopers.domain.example;
+package com.loopers.domain.user;
 
-import com.loopers.infrastructure.example.ExampleJpaRepository;
+import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import com.loopers.utils.DatabaseCleanUp;
@@ -16,12 +16,12 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @SpringBootTest
-class ExampleServiceIntegrationTest {
+class UserServiceIntegrationTest {
     @Autowired
-    private ExampleService exampleService;
+    private UserService exampleService;
 
     @Autowired
-    private ExampleJpaRepository exampleJpaRepository;
+    private UserJpaRepository userJpaRepository;
 
     @Autowired
     private DatabaseCleanUp databaseCleanUp;
@@ -38,19 +38,19 @@ class Get {
         @Test
         void returnsExampleInfo_whenValidIdIsProvided() {
             // arrange
-            ExampleModel exampleModel = exampleJpaRepository.save(
-                new ExampleModel("예시 제목", "예시 설명")
+            UserModel userModel = userJpaRepository.save(
+                new UserModel("예시 제목", "예시 설명")
             );
 
             // act
-            ExampleModel result = exampleService.getExample(exampleModel.getId());
+            UserModel result = exampleService.getExample(userModel.getId());
 
             // assert
             assertAll(
                 () -> assertThat(result).isNotNull(),
-                () -> assertThat(result.getId()).isEqualTo(exampleModel.getId()),
-                () -> assertThat(result.getName()).isEqualTo(exampleModel.getName()),
-                () -> assertThat(result.getDescription()).isEqualTo(exampleModel.getDescription())
+                () -> assertThat(result.getId()).isEqualTo(userModel.getId()),
+                () -> assertThat(result.getName()).isEqualTo(userModel.getName()),
+                () -> assertThat(result.getDescription()).isEqualTo(userModel.getDescription())
             );
         }
 
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/ExampleV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
similarity index 71%
rename from apps/commerce-api/src/test/java/com/loopers/interfaces/api/ExampleV1ApiE2ETest.java
rename to apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index 1bb3dba65..f04c9348a 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/ExampleV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -1,8 +1,8 @@
 package com.loopers.interfaces.api;
 
-import com.loopers.domain.example.ExampleModel;
-import com.loopers.infrastructure.example.ExampleJpaRepository;
-import com.loopers.interfaces.api.example.ExampleV1Dto;
+import com.loopers.domain.user.UserModel;
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.interfaces.api.user.UserV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.DisplayName;
@@ -24,22 +24,22 @@
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-class ExampleV1ApiE2ETest {
+class UserV1ApiE2ETest {
 
     private static final Function<Long, String> ENDPOINT_GET = id -> "/api/v1/examples/" + id;
 
     private final TestRestTemplate testRestTemplate;
-    private final ExampleJpaRepository exampleJpaRepository;
+    private final UserJpaRepository userJpaRepository;
     private final DatabaseCleanUp databaseCleanUp;
 
     @Autowired
-    public ExampleV1ApiE2ETest(
+    public UserV1ApiE2ETest(
         TestRestTemplate testRestTemplate,
-        ExampleJpaRepository exampleJpaRepository,
+        UserJpaRepository userJpaRepository,
         DatabaseCleanUp databaseCleanUp
     ) {
         this.testRestTemplate = testRestTemplate;
-        this.exampleJpaRepository = exampleJpaRepository;
+        this.userJpaRepository = userJpaRepository;
         this.databaseCleanUp = databaseCleanUp;
     }
 
@@ -55,22 +55,22 @@ class Get {
         @Test
         void returnsExampleInfo_whenValidIdIsProvided() {
             // arrange
-            ExampleModel exampleModel = exampleJpaRepository.save(
-                new ExampleModel("예시 제목", "예시 설명")
+            UserModel userModel = userJpaRepository.save(
+                new UserModel("예시 제목", "예시 설명")
             );
-            String requestUrl = ENDPOINT_GET.apply(exampleModel.getId());
+            String requestUrl = ENDPOINT_GET.apply(userModel.getId());
 
             // act
-            ParameterizedTypeReference<ApiResponse<ExampleV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<ExampleV1Dto.ExampleResponse>> response =
+            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
+            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
                 testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
 
             // assert
             assertAll(
                 () -> assertTrue(response.getStatusCode().is2xxSuccessful()),
-                () -> assertThat(response.getBody().data().id()).isEqualTo(exampleModel.getId()),
-                () -> assertThat(response.getBody().data().name()).isEqualTo(exampleModel.getName()),
-                () -> assertThat(response.getBody().data().description()).isEqualTo(exampleModel.getDescription())
+                () -> assertThat(response.getBody().data().id()).isEqualTo(userModel.getId()),
+                () -> assertThat(response.getBody().data().name()).isEqualTo(userModel.getName()),
+                () -> assertThat(response.getBody().data().description()).isEqualTo(userModel.getDescription())
             );
         }
 
@@ -81,8 +81,8 @@ void throwsBadRequest_whenIdIsNotProvided() {
             String requestUrl = "/api/v1/examples/나나";
 
             // act
-            ParameterizedTypeReference<ApiResponse<ExampleV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<ExampleV1Dto.ExampleResponse>> response =
+            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
+            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
                 testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
 
             // assert
@@ -100,8 +100,8 @@ void throwsException_whenInvalidIdIsProvided() {
             String requestUrl = ENDPOINT_GET.apply(invalidId);
 
             // act
-            ParameterizedTypeReference<ApiResponse<ExampleV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<ExampleV1Dto.ExampleResponse>> response =
+            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
+            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
                 testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
 
             // assert
diff --git a/http/commerce-api/user-v1.http b/http/commerce-api/user-v1.http
new file mode 100644
index 000000000..2a924d265
--- /dev/null
+++ b/http/commerce-api/user-v1.http
@@ -0,0 +1,2 @@
+### 예시 조회
+GET {{commerce-api}}/api/v1/examples/1
\ No newline at end of file

From e49838d0ca8c3480bb551f8f0f26e33e1b155103 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Mon, 27 Oct 2025 17:17:11 +0900
Subject: [PATCH 02/40] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?=
 =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EB=A6=AC?=
 =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- domain 패키지 관련
회원 도메인 엔티티와 관련된 속성 값, 입력값 검증 추가
- application 패키지
회원 도메인 엔티티 변경에 따른 애플리케이션 레이어 객체 정의 통일
- api 패키지
회원 API 관련 요청 및 응답값 통일
---
 .../loopers/application/user/UserInfo.java    |  19 ++-
 .../com/loopers/domain/user/UserModel.java    | 116 +++++++++++++++---
 .../interfaces/api/user/UserV1ApiSpec.java    |   2 +-
 .../interfaces/api/user/UserV1Controller.java |   6 +-
 .../interfaces/api/user/UserV1Dto.java        |  35 +++++-
 5 files changed, 147 insertions(+), 31 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
index 3ce78ca5a..60352c13b 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserInfo.java
@@ -2,12 +2,23 @@
 
 import com.loopers.domain.user.UserModel;
 
-public record UserInfo(Long id, String name, String description) {
+public record UserInfo(
+        String userId,
+        String userName,
+        String description,
+        String email,
+        String birthDate,
+        String gender,
+        Integer point) {
     public static UserInfo from(UserModel model) {
         return new UserInfo(
-            model.getId(),
-            model.getName(),
-            model.getDescription()
+            model.getUserId(),
+            model.getUserName(),
+            model.getDescription(),
+            model.getEmail(),
+            model.getBirthDate(),
+            model.getGender(),
+            model.getPoint()
         );
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 2b5f6b438..cb27363ad 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -3,42 +3,124 @@
 import com.loopers.domain.BaseEntity;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
+import jakarta.persistence.Column;
 import jakarta.persistence.Entity;
 import jakarta.persistence.Table;
+import lombok.Getter;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Pattern;
 
 @Entity
-@Table(name = "example")
+@Table(name = "user")
 public class UserModel extends BaseEntity {
-
-    private String name;
+    @Column(name = "user_id", unique = true, nullable = false)
+    private String userId;
+    @Column(name = "user_name", nullable = false)
+    private String userName;
     private String description;
+    private String email;
+    private String birthDate;
+    private String gender;
+    private Integer point;
 
     protected UserModel() {}
 
-    public UserModel(String name, String description) {
-        if (name == null || name.isBlank()) {
-            throw new CoreException(ErrorType.BAD_REQUEST, "이름은 비어있을 수 없습니다.");
-        }
-        if (description == null || description.isBlank()) {
+    public UserModel(String userId, String name, String description, String email, String birthDate, String gender) {
+        validateId(userId);
+        validateName(name);
+        validateEmail(email);
+        validateBirthDate(birthDate);
+        validateGender(gender);
+
+        this.userId = userId;
+        this.userName = name;
+        this.description = description;
+        this.email = email;
+        this.birthDate = birthDate;
+        this.gender = gender;
+        this.point = 0;
+    }
+
+    public void updateDescription(String newDescription) {
+        if (newDescription == null || newDescription.isBlank()) {
             throw new CoreException(ErrorType.BAD_REQUEST, "설명은 비어있을 수 없습니다.");
         }
+        this.description = newDescription;
+    }
 
-        this.name = name;
-        this.description = description;
+
+    private void validateNotBlank(String field, String message) {
+        if(field == null || field.isBlank()) {
+            throw new CoreException(ErrorType.BAD_REQUEST, message);
+        }
+    }
+
+    private void validateId(String id) {
+        validateNotBlank(id, "ID는 비어있을 수 없습니다.");
+        if(id.length() > 10) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "ID 길이는 10자리를 넘을 수 없습니다.");
+        }
+
+        String validUserIdRegex = "^[a-zA-Z0-9]+$";
+        if(!Pattern.matches(validUserIdRegex, id)) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자만 사용할 수 있습니다.");
+        }
     }
 
-    public String getName() {
-        return name;
+    private void validateName(String name) {
+        validateNotBlank(name, "이름은 비어있을 수 없습니다.");
+    }
+
+    private void validateEmail(String email) {
+        validateNotBlank(email, "이메일은 비어있을 수 없습니다.");
+
+        String validEmailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
+        if (!Pattern.matches(validEmailRegex, email)) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자만 사용할 수 있습니다.");
+
+        }
+    }
+
+    private void validateBirthDate(String birthDate) {
+        validateNotBlank(birthDate, "생년월일을 입력해주세요.");
+        try {
+            LocalDate.parse(birthDate, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        } catch (DateTimeException e) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "생년월일은 yyyy-MM-dd 형식이어야 합니다.");
+        }
+    }
+
+    private void validateGender(String gender) {
+        validateNotBlank(gender, "성별은 비어있을 수 없습니다.");
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public String getUserName() {
+        return userName;
     }
 
     public String getDescription() {
         return description;
     }
 
-    public void update(String newDescription) {
-        if (newDescription == null || newDescription.isBlank()) {
-            throw new CoreException(ErrorType.BAD_REQUEST, "설명은 비어있을 수 없습니다.");
-        }
-        this.description = newDescription;
+    public String getEmail() {
+        return email;
+    }
+
+    public String getBirthDate() {
+        return birthDate;
+    }
+
+    public String getGender() {
+        return gender;
+    }
+    public Integer getPoint() {
+        return point;
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index dfa383f1e..ed670194c 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -12,7 +12,7 @@ public interface UserV1ApiSpec {
         summary = "예시 조회",
         description = "ID로 예시를 조회합니다."
     )
-    ApiResponse<UserV1Dto.ExampleResponse> getExample(
+    ApiResponse<UserV1Dto.UserResponse> getExample(
         @Schema(name = "예시 ID", description = "조회할 예시의 ID")
         Long exampleId
     );
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index fa5d07d6f..f949dd1f5 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -11,18 +11,18 @@
 
 @RequiredArgsConstructor
 @RestController
-@RequestMapping("/api/v1/examples")
+@RequestMapping("/api/v1/users")
 public class UserV1Controller implements UserV1ApiSpec {
 
     private final UserFacade userFacade;
 
     @GetMapping("/{exampleId}")
     @Override
-    public ApiResponse<UserV1Dto.ExampleResponse> getExample(
+    public ApiResponse<UserV1Dto.UserResponse> getExample(
         @PathVariable(value = "exampleId") Long exampleId
     ) {
         UserInfo info = userFacade.getExample(exampleId);
-        UserV1Dto.ExampleResponse response = UserV1Dto.ExampleResponse.from(info);
+        UserV1Dto.UserResponse response = UserV1Dto.UserResponse.from(info);
         return ApiResponse.success(response);
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
index f0899a8bd..018309d46 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
@@ -3,12 +3,35 @@
 import com.loopers.application.user.UserInfo;
 
 public class UserV1Dto {
-    public record ExampleResponse(Long id, String name, String description) {
-        public static ExampleResponse from(UserInfo info) {
-            return new ExampleResponse(
-                info.id(),
-                info.name(),
-                info.description()
+
+    public record UserCreateRequest(
+            String userId,
+            String userName,
+            String description,
+            String email,
+            String birthDate,
+            String gender
+    ) { }
+
+
+    public record UserResponse(
+            String userId,
+            String userName,
+            String description,
+            String email,
+            String birthDate,
+            String gender,
+            Integer point
+    ) {
+        public static UserResponse from(UserInfo info) {
+            return new UserResponse(
+                    info.userId(),
+                    info.userName(),
+                    info.description(),
+                    info.email(),
+                    info.birthDate(),
+                    info.gender(),
+                    info.point()
             );
         }
     }

From fb0af55aacb49e90b530d7aef0f2eda9e7b951e7 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Mon, 27 Oct 2025 17:26:15 +0900
Subject: [PATCH 03/40] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EA=B0=80?=
 =?UTF-8?q?=EC=9E=85=20=EB=8B=A8=EC=9C=84=ED=85=8C=EC=8A=A4=ED=8A=B8=20?=
 =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

`UserModel 생성 테스트`

@Nested 를 활용한 회원 객체 생성에 관련된 @Test 시나리오 묶음
---
 .../loopers/domain/user/UserModelTest.java    | 198 ++++++++++++++++--
 1 file changed, 181 insertions(+), 17 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index d094bc0e8..154d1bd2c 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -2,6 +2,7 @@
 
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
+import jakarta.persistence.Column;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -11,55 +12,218 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 class UserModelTest {
-    @DisplayName("예시 모델을 생성할 때, ")
+    @DisplayName("UserModel 생성 테스트")
     @Nested
     class Create {
-        @DisplayName("제목과 설명이 모두 주어지면, 정상적으로 생성된다.")
+        @DisplayName("이용자ID, 이메일, 생년월일, 이용자이름, 성별 입력값이 유효하면 User 객체 생성에 성공한다")
         @Test
-        void createsExampleModel_whenNameAndDescriptionAreProvided() {
+        void createsExampleModel_whenAllRequiredFieldsAreProvidedAndValid() {
             // arrange
-            String name = "제목";
-            String description = "설명";
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
 
             // act
-            UserModel userModel = new UserModel(name, description);
+            UserModel userModel = new UserModel(userId, userName, description, email, birthDate, gender);
 
             // assert
             assertAll(
-                () -> assertThat(userModel.getId()).isNotNull(),
-                () -> assertThat(userModel.getName()).isEqualTo(name),
-                () -> assertThat(userModel.getDescription()).isEqualTo(description)
+                    () -> assertThat(userModel.getId()).isNotNull(),
+                    () -> assertThat(userModel.getUserName()).isEqualTo(userName),
+                    () -> assertThat(userModel.getDescription()).isEqualTo(description),
+                    () -> assertThat(userModel.getDescription()).isEqualTo(description),
+                    () -> assertThat(userModel.getEmail()).isEqualTo(email),
+                    () -> assertThat(userModel.getBirthDate()).isEqualTo(birthDate),
+                    () -> assertThat(userModel.getGender()).isEqualTo(gender),
+                    () -> assertThat(userModel.getPoint()).isEqualTo(0)
             );
         }
 
-        @DisplayName("제목이 빈칸으로만 이루어져 있으면, BAD_REQUEST 예외가 발생한다.")
+        @DisplayName("이용자ID가 10자를 초과했을 때 BAD_REQUEST를 반환한다")
         @Test
-        void throwsBadRequestException_whenTitleIsBlank() {
+        void throwsBadRequestException_whenUserIdLengthOverThanExpected() {
             // arrange
-            String name = "   ";
+            // arrange
+            String userId = "ajchoi12345";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("이용자ID가 영문 또는 숫자 외 문자를 포함할 때 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenUserIdContainsInvalidCharacters() {
+            // arrange
+            // arrange
+            String userId = "ajchoi_97";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("이용자ID가 빈 경우 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenUserIdNotValid() {
+            // arrange
+            String userId = " ";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("이메일 형식이 xx@yy.zz에 맞지 않으면 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenEmailNotValid() {
+            // arrange
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "xy@looperscom";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("이메일이 빈 경우 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenEmailIsBlank() {
+            // arrange
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("생년월일이 yyyy-MM-dd 포맷이 아니면 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenBirthDateNotValid() {
+            // arrange
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "1997/09/28";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
+        @DisplayName("생년월일이 빈 경우 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenBirthDateIsBlank() {
+            // arrange
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "";
+            String gender = "M";
+
+            // act
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender);
+            });
+
+            // assert
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+        // user_name, 성별,
+        @DisplayName("회원 이름이 빈 경우 BAD_REQUEST를 반환한다")
+        @Test
+        void throwsBadRequestException_whenUserNameIsBlank() {
+            // arrange
+            String userId = "ajchoi0928";
+            String userName = "";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
 
             // act
             CoreException result = assertThrows(CoreException.class, () -> {
-                new UserModel(name, "설명");
+                new UserModel(userId, userName, description, email, birthDate, gender);
             });
 
             // assert
             assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
         }
 
-        @DisplayName("설명이 비어있으면, BAD_REQUEST 예외가 발생한다.")
+        @DisplayName("성별이 빈 경우 BAD_REQUEST를 반환한다")
         @Test
-        void throwsBadRequestException_whenDescriptionIsEmpty() {
+        void throwsBadRequestException_whenGenderIsBlank() {
             // arrange
-            String description = "";
+            String userId = "ajchoi0928";
+            String userName = "최준호";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "";
 
             // act
             CoreException result = assertThrows(CoreException.class, () -> {
-                new UserModel("제목", description);
+                new UserModel(userId, userName, description, email, birthDate, gender);
             });
 
             // assert
             assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
         }
+
+
     }
 }

From 41c3d12e75698fc47dd169d40dda1d9f806149d4 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 01:23:48 +0900
Subject: [PATCH 04/40] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=EB=8F=84?=
 =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=ED=9A=8C=EC=9B=90?=
 =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C?=
 =?UTF-8?q?=EC=A7=81=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../loopers/application/user/UserCommand.java | 22 +++++++++++++++++++
 .../loopers/application/user/UserFacade.java  | 17 ++++++++++----
 .../com/loopers/domain/user/UserModel.java    | 18 +++++++++------
 .../loopers/domain/user/UserRepository.java   |  4 +++-
 .../com/loopers/domain/user/UserService.java  | 18 ++++++++++++---
 5 files changed, 64 insertions(+), 15 deletions(-)
 create mode 100644 apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java
new file mode 100644
index 000000000..9e4b45989
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java
@@ -0,0 +1,22 @@
+package com.loopers.application.user;
+
+import com.loopers.domain.user.UserModel;
+
+/**
+ * application에서 domain 레이어로 향하는 쓰기 관련 DTO들을 정의
+ */
+public class UserCommand {
+    public record Create (
+            String userId,
+            String userName,
+            String description,
+            String email,
+            String birthDate,
+            String gender,
+            Integer point
+    ) {
+        public UserModel toModel() {
+            return new UserModel(userId, userName, description, email, birthDate, gender);
+        }
+    }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 2d8e0f1b8..6f4d1ac5d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -2,16 +2,25 @@
 
 import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserService;
+import jakarta.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
 
 @RequiredArgsConstructor
 @Component
 public class UserFacade {
-    private final UserService exampleService;
+    private final UserService userService;
 
-    public UserInfo getExample(Long id) {
-        UserModel example = exampleService.getExample(id);
-        return UserInfo.from(example);
+    /**
+     * 사용자의 회원가입과 관련된 일련의 비즈니스 로직을 처리한다
+     * @param newUser
+     * @return
+     */
+    @Transactional
+    public UserInfo joinUser(UserCommand.Create newUser) {
+        UserModel resultUser = userService.createUser(newUser); // 실제 domain 계층 처리 결과
+        UserInfo userInfo =UserInfo.from(resultUser); // application 레이어용 불변 객체
+        // do sth
+        return userInfo;
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index cb27363ad..7bbcf05d4 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -26,9 +26,8 @@ public class UserModel extends BaseEntity {
     private String gender;
     private Integer point;
 
-    protected UserModel() {}
-
-    public UserModel(String userId, String name, String description, String email, String birthDate, String gender) {
+    public UserModel(String userId, String name, String description, String email
+            , String birthDate, String gender, Integer point) {
         validateId(userId);
         validateName(name);
         validateEmail(email);
@@ -41,7 +40,11 @@ public UserModel(String userId, String name, String description, String email, S
         this.email = email;
         this.birthDate = birthDate;
         this.gender = gender;
-        this.point = 0;
+        this.point = point;
+    }
+
+    public UserModel(String userId, String name, String description, String email, String birthDate, String gender) {
+        this(userId, name, description, email, birthDate, gender, 0);
     }
 
     public void updateDescription(String newDescription) {
@@ -53,19 +56,19 @@ public void updateDescription(String newDescription) {
 
 
     private void validateNotBlank(String field, String message) {
-        if(field == null || field.isBlank()) {
+        if (field == null || field.isBlank()) {
             throw new CoreException(ErrorType.BAD_REQUEST, message);
         }
     }
 
     private void validateId(String id) {
         validateNotBlank(id, "ID는 비어있을 수 없습니다.");
-        if(id.length() > 10) {
+        if (id.length() > 10) {
             throw new CoreException(ErrorType.BAD_REQUEST, "ID 길이는 10자리를 넘을 수 없습니다.");
         }
 
         String validUserIdRegex = "^[a-zA-Z0-9]+$";
-        if(!Pattern.matches(validUserIdRegex, id)) {
+        if (!Pattern.matches(validUserIdRegex, id)) {
             throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자만 사용할 수 있습니다.");
         }
     }
@@ -120,6 +123,7 @@ public String getBirthDate() {
     public String getGender() {
         return gender;
     }
+
     public Integer getPoint() {
         return point;
     }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
index a9400949b..b5ab50b24 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -3,5 +3,7 @@
 import java.util.Optional;
 
 public interface UserRepository {
-    Optional<UserModel> find(Long id);
+    boolean existsUserId(String userId);
+    Optional<UserModel> findByUserId(String userId);
+    UserModel save(UserModel user);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index e6aab9a6d..94af68869 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -1,5 +1,7 @@
 package com.loopers.domain.user;
 
+import com.loopers.application.user.UserCommand;
+import com.loopers.application.user.UserInfo;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
@@ -13,8 +15,18 @@ public class UserService {
     private final UserRepository userRepository;
 
     @Transactional(readOnly = true)
-    public UserModel getExample(Long id) {
-        return userRepository.find(id)
-            .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[id = " + id + "] 예시를 찾을 수 없습니다."));
+    public UserModel getExample(String userId) {
+        return userRepository.findByUserId(userId)
+            .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[userId = " + userId + "] 예시를 찾을 수 없습니다."));
+    }
+
+    @Transactional
+    public UserModel createUser(UserCommand.Create newUser) {
+
+        if(userRepository.existsUserId(newUser.userId())) {
+            throw new CoreException(ErrorType.CONFLICT, "이미 사용중인 이용자ID 입니다.");
+        }
+
+        return userRepository.save(newUser.toModel());
     }
 }

From 2c44dc59a42b3f29c1a1c9c3ed04da95cde5cdbb Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 01:55:28 +0900
Subject: [PATCH 05/40] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EA=B0=80?=
 =?UTF-8?q?=EC=9E=85=20=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9=ED=85=8C?=
 =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=ED=96=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/loopers/domain/user/UserModel.java    |  2 +
 .../user/UserJpaRepository.java               |  8 +-
 .../user/UserRepositoryImpl.java              | 14 ++-
 .../user/UserServiceIntegrationTest.java      | 95 +++++++++++++------
 4 files changed, 87 insertions(+), 32 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 7bbcf05d4..7f50ed5f7 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -26,6 +26,8 @@ public class UserModel extends BaseEntity {
     private String gender;
     private Integer point;
 
+    protected UserModel() {}
+    
     public UserModel(String userId, String name, String description, String email
             , String birthDate, String gender, Integer point) {
         validateId(userId);
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
index 7fd328068..dba86581c 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
@@ -3,4 +3,10 @@
 import com.loopers.domain.user.UserModel;
 import org.springframework.data.jpa.repository.JpaRepository;
 
-public interface UserJpaRepository extends JpaRepository<UserModel, Long> {}
+import java.util.Optional;
+
+public interface UserJpaRepository extends JpaRepository<UserModel, Long> {
+    boolean existsByUserId(String userId);
+
+    Optional<UserModel> findByUserId(String userId);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
index 499841f39..de08e3150 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -13,7 +13,17 @@ public class UserRepositoryImpl implements UserRepository {
     private final UserJpaRepository userJpaRepository;
 
     @Override
-    public Optional<UserModel> find(Long id) {
-        return userJpaRepository.findById(id);
+    public boolean existsUserId(String userId) {
+        return userJpaRepository.existsByUserId(userId);
+    }
+
+    @Override
+    public Optional<UserModel> findByUserId(String userId) {
+        return userJpaRepository.findByUserId(userId);
+    }
+
+    @Override
+    public UserModel save(UserModel user) {
+        return userJpaRepository.save(user);
     }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index fb7ed1732..f7a1eb322 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -1,6 +1,6 @@
 package com.loopers.domain.user;
 
-import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.application.user.UserCommand;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import com.loopers.utils.DatabaseCleanUp;
@@ -10,18 +10,22 @@
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
 
-import static org.assertj.core.api.Assertions.assertThat;
+import java.util.Optional;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.BDDMockito.given;
 
 @SpringBootTest
 class UserServiceIntegrationTest {
     @Autowired
-    private UserService exampleService;
+    private UserService userService;
 
     @Autowired
-    private UserJpaRepository userJpaRepository;
+    private UserRepository userRepository;
 
     @Autowired
     private DatabaseCleanUp databaseCleanUp;
@@ -31,42 +35,75 @@ void tearDown() {
         databaseCleanUp.truncateAllTables();
     }
 
-    @DisplayName("예시를 조회할 때,")
+    /**
+     * - [x]  회원 가입시 User 저장이 수행된다. ( spy 검증 )
+     * - [x]  이미 가입된 ID 로 회원가입 시도 시, 실패한다
+     */
+
+    @DisplayName("회원 가입할 때")
     @Nested
-    class Get {
-        @DisplayName("존재하는 예시 ID를 주면, 해당 예시 정보를 반환한다.")
+    class CreateUser {
+
+        @DisplayName("유효한 입력값을 갖는 신규 회원 정보로 회원가입이 수행된다")
         @Test
-        void returnsExampleInfo_whenValidIdIsProvided() {
-            // arrange
-            UserModel userModel = userJpaRepository.save(
-                new UserModel("예시 제목", "예시 설명")
-            );
+        void saveUser_whenAllRequiredFieldsAreProvidedAndValid() {
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            UserCommand.Create create = new UserCommand.Create(userId, userName
+                    , description, email, birthDate, gender, 0);
 
-            // act
-            UserModel result = exampleService.getExample(userModel.getId());
+            // when
+            UserModel result = userService.createUser(create);
+            int defaultPoint = 0;
 
-            // assert
+            // then
             assertAll(
-                () -> assertThat(result).isNotNull(),
-                () -> assertThat(result.getId()).isEqualTo(userModel.getId()),
-                () -> assertThat(result.getName()).isEqualTo(userModel.getName()),
-                () -> assertThat(result.getDescription()).isEqualTo(userModel.getDescription())
+                    () -> assertThat(result).isNotNull(),
+                    () -> assertThat(result.getUserId()).isEqualTo(userId),
+                    () -> assertThat(result.getUserName()).isEqualTo(userName),
+                    () -> assertThat(result.getDescription()).isEqualTo(description),
+                    () -> assertThat(result.getEmail()).isEqualTo(email),
+                    () -> assertThat(result.getBirthDate()).isEqualTo(birthDate),
+                    () -> assertThat(result.getGender()).isEqualTo(gender),
+                    () -> assertThat(result.getPoint()).isEqualTo(defaultPoint)
             );
+
+            Optional<UserModel> selectUser = userRepository.findByUserId(userId);
+            assertThat(selectUser).isPresent();
+            assertThat(selectUser.get().getUserId()).isEqualTo(userId);
         }
 
-        @DisplayName("존재하지 않는 예시 ID를 주면, NOT_FOUND 예외가 발생한다.")
+
+       @DisplayName("이미 가입된 ID로 회원가입 시도시 실패한다")
         @Test
-        void throwsException_whenInvalidIdIsProvided() {
-            // arrange
-            Long invalidId = 999L; // Assuming this ID does not exist
+        void throwsConflictException_whenDuplicatedIdIsProvided() {
+           // given
+           String userId = "ajchoi0928";
+           String userName = "junho";
+           String description = "loopers backend developer";
+           String email = "loopers@loopers.com";
+           String birthDate = "1997-09-28";
+           String gender = "M";
+
+           UserCommand.Create create = new UserCommand.Create(userId, userName
+                   , description, email, birthDate, gender, 0);
+
+           UserModel someUser = userService.createUser(create);
 
-            // act
-            CoreException exception = assertThrows(CoreException.class, () -> {
-                exampleService.getExample(invalidId);
-            });
+           // when
+           CoreException result = assertThrows(CoreException.class, () -> {
+               userService.createUser(create);
+           });
 
-            // assert
-            assertThat(exception.getErrorType()).isEqualTo(ErrorType.NOT_FOUND);
+           // then
+           assertThat(result.getErrorType()).isEqualTo(ErrorType.CONFLICT);
+           assertThat(result.getMessage()).isEqualTo("이미 사용중인 이용자ID 입니다.");
         }
     }
 }

From 172c8c6ec85abecc6fe2ed36d5efbe2a8908f9b9 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 01:55:42 +0900
Subject: [PATCH 06/40] =?UTF-8?q?docs:=201=EC=A3=BC=EC=B0=A8=20=EC=BD=94?=
 =?UTF-8?q?=EB=94=A9=20=EA=B3=BC=EC=A0=9C=20=EC=A7=84=EC=B2=99=EA=B8=B0?=
 =?UTF-8?q?=EB=A1=9D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .codeguide/loopers-1-week.md | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/.codeguide/loopers-1-week.md b/.codeguide/loopers-1-week.md
index a8ace53e5..9821d259d 100644
--- a/.codeguide/loopers-1-week.md
+++ b/.codeguide/loopers-1-week.md
@@ -6,14 +6,14 @@
 
 **🧱 단위 테스트**
 
-- [ ]  ID 가 `영문 및 숫자 10자 이내` 형식에 맞지 않으면, User 객체 생성에 실패한다.
-- [ ]  이메일이 `xx@yy.zz` 형식에 맞지 않으면, User 객체 생성에 실패한다.
-- [ ]  생년월일이 `yyyy-MM-dd` 형식에 맞지 않으면, User 객체 생성에 실패한다.
+- [x]  ID 가 `영문 및 숫자 10자 이내` 형식에 맞지 않으면, User 객체 생성에 실패한다.
+- [x]  이메일이 `xx@yy.zz` 형식에 맞지 않으면, User 객체 생성에 실패한다.
+- [x]  생년월일이 `yyyy-MM-dd` 형식에 맞지 않으면, User 객체 생성에 실패한다.
 
 **🔗 통합 테스트**
 
-- [ ]  회원 가입시 User 저장이 수행된다. ( spy 검증 )
-- [ ]  이미 가입된 ID 로 회원가입 시도 시, 실패한다.
+- [x]  회원 가입시 User 저장이 수행된다. ( spy 검증 )
+- [x]  이미 가입된 ID 로 회원가입 시도 시, 실패한다
 
 **🌐 E2E 테스트**
 

From 3cac3c66ea7c032aa8fcbc08dee32b2b150631d4 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 21:36:06 +0900
Subject: [PATCH 07/40] =?UTF-8?q?feat:=20UserV1=20API=20-=20=ED=9A=8C?=
 =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=AA=85=EC=84=B8=20=EB=B0=8F=20?=
 =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../interfaces/api/user/UserV1ApiSpec.java    | 15 ++++++++++++-
 .../interfaces/api/user/UserV1Controller.java | 21 +++++++++----------
 .../interfaces/api/user/UserV1Dto.java        | 11 +++++++++-
 3 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index ed670194c..e6316ebd4 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -8,12 +8,25 @@
 @Tag(name = "Example V1 API", description = "Loopers 예시 API 입니다.")
 public interface UserV1ApiSpec {
 
-    @Operation(
+    /*@Operation(
         summary = "예시 조회",
         description = "ID로 예시를 조회합니다."
     )
     ApiResponse<UserV1Dto.UserResponse> getExample(
         @Schema(name = "예시 ID", description = "조회할 예시의 ID")
         Long exampleId
+    );*/
+
+
+    @Operation(
+            summary = "회원가입",
+            description = "UserV1Dto.UserCreateRequest 포맷으로 회원가입을 처리합니다."
+    )
+    ApiResponse<UserV1Dto.UserResponse> signUp(
+            @Schema(name = "", description = "조회할 예시의 ID")
+            UserV1Dto.UserCreateRequest userCreateRequest
     );
+
+
+
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index f949dd1f5..df99f2f9b 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -1,28 +1,27 @@
 package com.loopers.interfaces.api.user;
 
+import com.loopers.application.user.UserCommand;
 import com.loopers.application.user.UserFacade;
 import com.loopers.application.user.UserInfo;
 import com.loopers.interfaces.api.ApiResponse;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 @RequiredArgsConstructor
 @RestController
-@RequestMapping("/api/v1/users")
+@RequestMapping("/api/v1/user")
 public class UserV1Controller implements UserV1ApiSpec {
 
     private final UserFacade userFacade;
 
-    @GetMapping("/{exampleId}")
+    @PostMapping
     @Override
-    public ApiResponse<UserV1Dto.UserResponse> getExample(
-        @PathVariable(value = "exampleId") Long exampleId
+    public ApiResponse<UserV1Dto.UserResponse> signUp(
+            @RequestBody @Valid UserV1Dto.UserCreateRequest userCreateRequest
     ) {
-        UserInfo info = userFacade.getExample(exampleId);
-        UserV1Dto.UserResponse response = UserV1Dto.UserResponse.from(info);
-        return ApiResponse.success(response);
+        UserCommand.Create newUserCmd = userCreateRequest.toCommand();
+        UserInfo info = userFacade.joinUser(newUserCmd); // 예외 발생시 ApiControllerAdvice 클래스에서 처리
+        return ApiResponse.success(UserV1Dto.UserResponse.from(info));
     }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
index 018309d46..fe4ab151c 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
@@ -1,5 +1,6 @@
 package com.loopers.interfaces.api.user;
 
+import com.loopers.application.user.UserCommand;
 import com.loopers.application.user.UserInfo;
 
 public class UserV1Dto {
@@ -11,7 +12,15 @@ public record UserCreateRequest(
             String email,
             String birthDate,
             String gender
-    ) { }
+    ) {
+        /**
+         * interfaces -> application 의존 OK(안쪽 의존)
+         * @return
+         */
+        public UserCommand.Create toCommand() {
+            return new UserCommand.Create(userId, userName, description, email, birthDate, gender, 0);
+        }
+    }
 
 
     public record UserResponse(

From 3b0fbf106bcf8141bb0649cce336538fdf607ca1 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 21:36:50 +0900
Subject: [PATCH 08/40] =?UTF-8?q?test:=20=EA=B8=B0=EB=B3=B8=20=ED=85=8C?=
 =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../interfaces/api/UserV1ApiE2ETest.java      | 110 ++++++++++++++++--
 1 file changed, 99 insertions(+), 11 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index f04c9348a..d5f9e9c6a 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -1,6 +1,5 @@
 package com.loopers.interfaces.api;
 
-import com.loopers.domain.user.UserModel;
 import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.interfaces.api.user.UserV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
@@ -12,31 +11,29 @@
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.client.TestRestTemplate;
 import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.*;
+import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
 
 import java.util.function.Function;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertAll;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 class UserV1ApiE2ETest {
 
     private static final Function<Long, String> ENDPOINT_GET = id -> "/api/v1/examples/" + id;
+    private static final String ENDPOINT_SIGNUP = "/api/v1/user";
 
     private final TestRestTemplate testRestTemplate;
     private final UserJpaRepository userJpaRepository;
     private final DatabaseCleanUp databaseCleanUp;
 
+
     @Autowired
     public UserV1ApiE2ETest(
-        TestRestTemplate testRestTemplate,
-        UserJpaRepository userJpaRepository,
-        DatabaseCleanUp databaseCleanUp
+            TestRestTemplate testRestTemplate,
+            UserJpaRepository userJpaRepository,
+            DatabaseCleanUp databaseCleanUp
     ) {
         this.testRestTemplate = testRestTemplate;
         this.userJpaRepository = userJpaRepository;
@@ -48,10 +45,15 @@ void tearDown() {
         databaseCleanUp.truncateAllTables();
     }
 
+    /*
+    - [x]  회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.
+    - [x]  회원 가입 시에 성별이 없을 경우, `400 Bad Request` 응답을 반환한다.
+     */
+
     @DisplayName("GET /api/v1/examples/{id}")
     @Nested
     class Get {
-        @DisplayName("존재하는 예시 ID를 주면, 해당 예시 정보를 반환한다.")
+        /*@DisplayName("존재하는 예시 ID를 주면, 해당 예시 정보를 반환한다.")
         @Test
         void returnsExampleInfo_whenValidIdIsProvided() {
             // arrange
@@ -109,6 +111,92 @@ void throwsException_whenInvalidIdIsProvided() {
                 () -> assertTrue(response.getStatusCode().is4xxClientError()),
                 () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND)
             );
+        }*/
+    }
+
+    @DisplayName("POST /api/v1/user")
+    @Nested
+    class Post {
+
+        @DisplayName("회원가입 성공시, 생성된 유저 정보를 응답으로 반환한다")
+        @Test
+        void returnUserResponse_whenSuccessful() {
+            // given
+            UserV1Dto.UserCreateRequest request = new UserV1Dto.UserCreateRequest(
+                    "ajchoi0928",
+                    "junho",
+                    "loopers backend developer",
+                    "loopers@loopers.com",
+                    "1997-09-28",
+                    "M"
+            );
+
+            // var json = objectMapper.writeValueAsString(request);
+
+            // when
+            ResponseEntity<ApiResponse<UserV1Dto.UserResponse>> response =
+                    testRestTemplate.exchange(ENDPOINT_SIGNUP,
+                            HttpMethod.POST,
+                            json(request),
+                            new ParameterizedTypeReference<ApiResponse<UserV1Dto.UserResponse>>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
+
+            var data = response.getBody().data();
+            assertThat(data).isNotNull();
+            assertThat(data.userId()).isEqualTo("ajchoi0928");
+            assertThat(data.userName()).isEqualTo("junho");
+            assertThat(data.email()).isEqualTo("loopers@loopers.com");
+
+            // DB 검증(선택)
+            assertThat(userJpaRepository.findByUserId("ajchoi0928")).isPresent();
         }
+
+        @DisplayName("회원가입 시 성별 정보가 없는 경우 '400 Bad Reqeuset' 응답을 반환한다")
+        @Test
+        void throwsException_whenBlankGenderIsProvided() {
+            // given
+            UserV1Dto.UserCreateRequest request = new UserV1Dto.UserCreateRequest(
+                    "ajchoi0928",
+                    "junho",
+                    "loopers backend developer",
+                    "loopers@loopers.com",
+                    "1997-09-28",
+                    ""
+            );
+
+            // when
+            ResponseEntity<ApiResponse<UserV1Dto.UserResponse>> response =
+                    testRestTemplate.exchange(ENDPOINT_SIGNUP,
+                            HttpMethod.POST,
+                            json(request),
+                            new ParameterizedTypeReference<ApiResponse<UserV1Dto.UserResponse>>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.FAIL);
+            assertThat(response.getBody().meta().errorCode()).isNotBlank();
+            assertThat(response.getBody().meta().message()).isNotBlank();
+
+
+        }
+
+
+        private static HttpEntity<Object> json(Object body) {
+            HttpHeaders h = new HttpHeaders();
+            h.setContentType(MediaType.APPLICATION_JSON);
+            return new HttpEntity<>(body, h);
+        }
+
     }
 }

From e6aa08c0551fc10a32d922d572887d265db6218f Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 21:53:40 +0900
Subject: [PATCH 09/40] =?UTF-8?q?test:=20=ED=9A=8C=EC=9B=90=EA=B0=80?=
 =?UTF-8?q?=EC=9E=85=20E2E=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?=
 =?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- [x]  회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.
- [x]  회원 가입 시에 성별이 없을 경우, `400 Bad Request` 응답을 반환한다.
---
 .../java/com/loopers/interfaces/api/UserV1ApiE2ETest.java   | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index d5f9e9c6a..c25e65465 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -138,7 +138,7 @@ void returnUserResponse_whenSuccessful() {
                     testRestTemplate.exchange(ENDPOINT_SIGNUP,
                             HttpMethod.POST,
                             json(request),
-                            new ParameterizedTypeReference<ApiResponse<UserV1Dto.UserResponse>>() {
+                            new ParameterizedTypeReference<>() {
                             }
                     );
 
@@ -154,7 +154,7 @@ void returnUserResponse_whenSuccessful() {
             assertThat(data.userName()).isEqualTo("junho");
             assertThat(data.email()).isEqualTo("loopers@loopers.com");
 
-            // DB 검증(선택)
+            // DB 검증
             assertThat(userJpaRepository.findByUserId("ajchoi0928")).isPresent();
         }
 
@@ -176,7 +176,7 @@ void throwsException_whenBlankGenderIsProvided() {
                     testRestTemplate.exchange(ENDPOINT_SIGNUP,
                             HttpMethod.POST,
                             json(request),
-                            new ParameterizedTypeReference<ApiResponse<UserV1Dto.UserResponse>>() {
+                            new ParameterizedTypeReference<>() {
                             }
                     );
 

From 25b4289d7f8d20daee08ec0be88d1d7b9e78b05c Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 23:03:05 +0900
Subject: [PATCH 10/40] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?=
 =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=82=AC?=
 =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/java/com/loopers/application/user/UserFacade.java  | 6 ++++++
 .../main/java/com/loopers/domain/user/UserRepository.java   | 1 +
 .../src/main/java/com/loopers/domain/user/UserService.java  | 6 +++---
 .../com/loopers/infrastructure/user/UserJpaRepository.java  | 2 ++
 4 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 6f4d1ac5d..5311591f0 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -23,4 +23,10 @@ public UserInfo joinUser(UserCommand.Create newUser) {
         // do sth
         return userInfo;
     }
+
+    @Transactional
+    public UserInfo getUserInfo(String userId) {
+        UserModel model = userService.getUserOrNull(userId);
+        return model == null ? null : UserInfo.from(model);
+    }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
index b5ab50b24..847d20dea 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserRepository.java
@@ -6,4 +6,5 @@ public interface UserRepository {
     boolean existsUserId(String userId);
     Optional<UserModel> findByUserId(String userId);
     UserModel save(UserModel user);
+    boolean deleteUser(String userId);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 94af68869..a2d9f6d65 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -15,9 +15,9 @@ public class UserService {
     private final UserRepository userRepository;
 
     @Transactional(readOnly = true)
-    public UserModel getExample(String userId) {
-        return userRepository.findByUserId(userId)
-            .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "[userId = " + userId + "] 예시를 찾을 수 없습니다."));
+    public UserModel getUserOrNull(String userId) {
+        return userRepository.findByUserId(userId).orElse(null);
+
     }
 
     @Transactional
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
index dba86581c..278cb3149 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserJpaRepository.java
@@ -9,4 +9,6 @@ public interface UserJpaRepository extends JpaRepository<UserModel, Long> {
     boolean existsByUserId(String userId);
 
     Optional<UserModel> findByUserId(String userId);
+
+    int deleteByUserId(String userId);
 }

From 775347b75640c06ced1f7086ad446826e8223935 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 23:03:10 +0900
Subject: [PATCH 11/40] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?=
 =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=82=AC?=
 =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=82=AD=EC=A0=9C=20=EB=A1=9C=EC=A7=81=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/loopers/infrastructure/user/UserRepositoryImpl.java  | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
index de08e3150..2d15c3aa6 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/user/UserRepositoryImpl.java
@@ -26,4 +26,9 @@ public Optional<UserModel> findByUserId(String userId) {
     public UserModel save(UserModel user) {
         return userJpaRepository.save(user);
     }
+
+    @Override
+    public boolean deleteUser(String userId) {
+        return userJpaRepository.deleteByUserId(userId) > 0;
+    }
 }

From b20e5d53f39e884044b81b732b78db5afd2d27d7 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Thu, 30 Oct 2025 23:03:45 +0900
Subject: [PATCH 12/40] =?UTF-8?q?test:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4?=
 =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.
- 해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
---
 .../user/UserServiceIntegrationTest.java      | 106 ++++++++++++++----
 1 file changed, 84 insertions(+), 22 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index f7a1eb322..a3eb461d8 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -10,6 +10,7 @@
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.context.bean.override.mockito.MockitoBean;
 
 import java.util.Optional;
@@ -18,6 +19,7 @@
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.when;
 
 @SpringBootTest
 class UserServiceIntegrationTest {
@@ -27,6 +29,9 @@ class UserServiceIntegrationTest {
     @Autowired
     private UserRepository userRepository;
 
+    /*@MockitoBean
+    private UserRepository userRepositoryMock;*/
+
     @Autowired
     private DatabaseCleanUp databaseCleanUp;
 
@@ -38,6 +43,10 @@ void tearDown() {
     /**
      * - [x]  회원 가입시 User 저장이 수행된다. ( spy 검증 )
      * - [x]  이미 가입된 ID 로 회원가입 시도 시, 실패한다
+     * <p>
+     * ### 내 정보 조회
+     * - [x]  해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.
+     * - [x]  해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
      */
 
     @DisplayName("회원 가입할 때")
@@ -80,30 +89,83 @@ void saveUser_whenAllRequiredFieldsAreProvidedAndValid() {
         }
 
 
-       @DisplayName("이미 가입된 ID로 회원가입 시도시 실패한다")
+        @DisplayName("이미 가입된 ID로 회원가입 시도시 실패한다")
         @Test
         void throwsConflictException_whenDuplicatedIdIsProvided() {
-           // given
-           String userId = "ajchoi0928";
-           String userName = "junho";
-           String description = "loopers backend developer";
-           String email = "loopers@loopers.com";
-           String birthDate = "1997-09-28";
-           String gender = "M";
-
-           UserCommand.Create create = new UserCommand.Create(userId, userName
-                   , description, email, birthDate, gender, 0);
-
-           UserModel someUser = userService.createUser(create);
-
-           // when
-           CoreException result = assertThrows(CoreException.class, () -> {
-               userService.createUser(create);
-           });
-
-           // then
-           assertThat(result.getErrorType()).isEqualTo(ErrorType.CONFLICT);
-           assertThat(result.getMessage()).isEqualTo("이미 사용중인 이용자ID 입니다.");
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+
+            UserCommand.Create create = new UserCommand.Create(userId, userName
+                    , description, email, birthDate, gender, 0);
+
+            UserModel someUser = userService.createUser(create);
+
+            // when
+            CoreException result = assertThrows(CoreException.class, () -> {
+                userService.createUser(create);
+            });
+
+            // then
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.CONFLICT);
+            assertThat(result.getMessage()).isEqualTo("이미 사용중인 이용자ID 입니다.");
         }
+
+        @DisplayName("해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다")
+        @Test
+        void getUserById_whenExistUserIdIsProvided() {
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+            int defaultPoint = 0;
+
+            UserCommand.Create create = new UserCommand.Create(userId, userName
+                    , description, email, birthDate, gender, 0);
+
+            UserModel someUser = userService.createUser(create);
+
+            // when
+            UserModel foundUser = userService.getUserOrNull(userId);
+
+            // then
+            assertAll(
+                    () -> assertThat(foundUser).isNotNull(),
+                    () -> assertThat(foundUser.getUserId()).isEqualTo(userId),
+                    () -> assertThat(foundUser.getUserName()).isEqualTo(userName),
+                    () -> assertThat(foundUser.getDescription()).isEqualTo(description),
+                    () -> assertThat(foundUser.getEmail()).isEqualTo(email),
+                    () -> assertThat(foundUser.getBirthDate()).isEqualTo(birthDate),
+                    () -> assertThat(foundUser.getGender()).isEqualTo(gender),
+                    () -> assertThat(foundUser.getPoint()).isEqualTo(defaultPoint)
+            );
+
+        }
+
+        @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.")
+        @Test
+        void getUserById_whenNotExistUserIdIsProvided() {
+            // given
+            String userId = "ajchoi0928";
+
+            // when(userRepositoryMock.findByUserId(userId)).thenReturn(Optional.empty());
+            if(userRepository.existsUserId(userId)) {
+                userRepository.deleteUser(userId);
+            }
+
+            // when
+            UserModel foundUser = userService.getUserOrNull(userId);
+
+            // then
+            assertThat(foundUser).isNull();
+        }
+
     }
 }

From 89e00789c7ae17a9886506331718890275299963 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 00:02:53 +0900
Subject: [PATCH 13/40] =?UTF-8?q?fix:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=B9=84?=
 =?UTF-8?q?=EC=8A=A4=EB=A0=88=EC=9D=B4=EC=96=B4=20=EC=98=88=EC=99=B8?=
 =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 해당 ID 의 회원이 존재하지 않을 경우, ErrorType.NOT_FOUND 반환
---
 .../main/java/com/loopers/application/user/UserFacade.java | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 5311591f0..4ed7e53e2 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -2,6 +2,8 @@
 
 import com.loopers.domain.user.UserModel;
 import com.loopers.domain.user.UserService;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
 import jakarta.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
@@ -27,6 +29,9 @@ public UserInfo joinUser(UserCommand.Create newUser) {
     @Transactional
     public UserInfo getUserInfo(String userId) {
         UserModel model = userService.getUserOrNull(userId);
-        return model == null ? null : UserInfo.from(model);
+        if(model == null) {
+            throw new CoreException(ErrorType.NOT_FOUND, "이용자ID를 확인해주세요");
+        }
+        return UserInfo.from(model);
     }
 }

From 1d388fe912024d4ae6d311791ba1c887e4bbd8b4 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 00:03:38 +0900
Subject: [PATCH 14/40] =?UTF-8?q?feat:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20API=20=EC=8A=A4=ED=8E=99=20=EB=B0=8F=20?=
 =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../loopers/interfaces/api/user/UserV1ApiSpec.java   | 12 ++++++++++++
 .../interfaces/api/user/UserV1Controller.java        |  7 +++++++
 2 files changed, 19 insertions(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index e6316ebd4..4b96eab6e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -4,6 +4,7 @@
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
 
 @Tag(name = "Example V1 API", description = "Loopers 예시 API 입니다.")
 public interface UserV1ApiSpec {
@@ -27,6 +28,17 @@ ApiResponse<UserV1Dto.UserResponse> signUp(
             UserV1Dto.UserCreateRequest userCreateRequest
     );
 
+    @Operation(
+            summary = "내 정보 조회",
+            description = "userId에 대한 회원정보를 조회합니다."
+    )
+    ApiResponse<UserV1Dto.UserResponse> getUser (
+            @Schema(name = "userId",
+                    description = "조회할 예시의 ID",
+                    example = "ajchoi0928")
+            @PathVariable String userId
+    );
+
 
 
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index df99f2f9b..3515bff63 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -24,4 +24,11 @@ public ApiResponse<UserV1Dto.UserResponse> signUp(
         UserInfo info = userFacade.joinUser(newUserCmd); // 예외 발생시 ApiControllerAdvice 클래스에서 처리
         return ApiResponse.success(UserV1Dto.UserResponse.from(info));
     }
+
+    @GetMapping("/{userId}")
+    @Override
+    public ApiResponse<UserV1Dto.UserResponse> getUser(@PathVariable("userId") String userId) {
+        UserInfo userInfo = userFacade.getUserInfo(userId);
+        return ApiResponse.success(UserV1Dto.UserResponse.from(userInfo));
+    }
 }

From f512bcdf1a104579a8470bca646d255646b01877 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 00:04:06 +0900
Subject: [PATCH 15/40] =?UTF-8?q?test:=20=EB=82=B4=20=EC=A0=95=EB=B3=B4?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20E2E=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

- 내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다.
- 존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다.
---
 .../interfaces/api/UserV1ApiE2ETest.java      | 142 ++++++++++--------
 1 file changed, 76 insertions(+), 66 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index c25e65465..99b37199b 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -1,5 +1,6 @@
 package com.loopers.interfaces.api;
 
+import com.loopers.domain.user.UserModel;
 import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.interfaces.api.user.UserV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
@@ -12,7 +13,6 @@
 import org.springframework.boot.test.web.client.TestRestTemplate;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.*;
-import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
 
 import java.util.function.Function;
 
@@ -21,14 +21,13 @@
 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
 class UserV1ApiE2ETest {
 
-    private static final Function<Long, String> ENDPOINT_GET = id -> "/api/v1/examples/" + id;
+    private static final Function<String, String> ENDPOINT_GETUSER = id -> "/api/v1/user/" + id;
     private static final String ENDPOINT_SIGNUP = "/api/v1/user";
 
     private final TestRestTemplate testRestTemplate;
     private final UserJpaRepository userJpaRepository;
     private final DatabaseCleanUp databaseCleanUp;
 
-
     @Autowired
     public UserV1ApiE2ETest(
             TestRestTemplate testRestTemplate,
@@ -46,72 +45,77 @@ void tearDown() {
     }
 
     /*
+    # E2E_회원 가입
     - [x]  회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.
     - [x]  회원 가입 시에 성별이 없을 경우, `400 Bad Request` 응답을 반환한다.
+    # E2E_내 정보 조회
+    - [x]  내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다.
+    - [x]  존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다.
+
      */
 
-    @DisplayName("GET /api/v1/examples/{id}")
+    @DisplayName("GET /api/v1/user/{id}")
     @Nested
     class Get {
-        /*@DisplayName("존재하는 예시 ID를 주면, 해당 예시 정보를 반환한다.")
+        @DisplayName("내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다")
         @Test
-        void returnsExampleInfo_whenValidIdIsProvided() {
-            // arrange
-            UserModel userModel = userJpaRepository.save(
-                new UserModel("예시 제목", "예시 설명")
-            );
-            String requestUrl = ENDPOINT_GET.apply(userModel.getId());
-
-            // act
-            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
-                testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
-
-            // assert
-            assertAll(
-                () -> assertTrue(response.getStatusCode().is2xxSuccessful()),
-                () -> assertThat(response.getBody().data().id()).isEqualTo(userModel.getId()),
-                () -> assertThat(response.getBody().data().name()).isEqualTo(userModel.getName()),
-                () -> assertThat(response.getBody().data().description()).isEqualTo(userModel.getDescription())
-            );
-        }
+        void returnsUserInfo_whenValidUserIdIsProvided() {
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+            UserModel userModel = new UserModel(userId, userName, description, email, birthDate, gender);
+            userJpaRepository.save(userModel);
+
+            //when
+            ResponseEntity<ApiResponse<UserV1Dto.UserResponse>> response =
+                    testRestTemplate.exchange(ENDPOINT_GETUSER.apply(userId),
+                            HttpMethod.GET,
+                            new HttpEntity<>(null),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
 
-        @DisplayName("숫자가 아닌 ID 로 요청하면, 400 BAD_REQUEST 응답을 받는다.")
-        @Test
-        void throwsBadRequest_whenIdIsNotProvided() {
-            // arrange
-            String requestUrl = "/api/v1/examples/나나";
-
-            // act
-            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
-                testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
-
-            // assert
-            assertAll(
-                () -> assertTrue(response.getStatusCode().is4xxClientError()),
-                () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)
-            );
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
+
+            var data = response.getBody().data();
+            assertThatSameValue(data, userModel);
         }
 
-        @DisplayName("존재하지 않는 예시 ID를 주면, 404 NOT_FOUND 응답을 받는다.")
+        @DisplayName("존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다")
         @Test
-        void throwsException_whenInvalidIdIsProvided() {
-            // arrange
-            Long invalidId = -1L;
-            String requestUrl = ENDPOINT_GET.apply(invalidId);
-
-            // act
-            ParameterizedTypeReference<ApiResponse<UserV1Dto.ExampleResponse>> responseType = new ParameterizedTypeReference<>() {};
-            ResponseEntity<ApiResponse<UserV1Dto.ExampleResponse>> response =
-                testRestTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<>(null), responseType);
-
-            // assert
-            assertAll(
-                () -> assertTrue(response.getStatusCode().is4xxClientError()),
-                () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND)
-            );
-        }*/
+        void throwsNotFoundException_whenInvalidUserIdIsProvided() {
+            // given
+            String userId = "ajchoi0928";
+            // 테스트 긴한데 DELETE 날려도 괜찮을까? DELETE 치는 건 mock 해야하나?
+            userJpaRepository.deleteByUserId(userId);
+
+            // when
+            ResponseEntity<ApiResponse<UserV1Dto.UserResponse>> response =
+                    testRestTemplate.exchange(ENDPOINT_GETUSER.apply(userId),
+                            HttpMethod.GET,
+                            new HttpEntity<>(null),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+            // [WARN] 에러일 때도 @ControllerAdvice가 ApiResponse.fail(...) 바디를 만듦 주의
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.FAIL);
+            assertThat(response.getBody().data()).isNull();
+            // 에러메시지 내용 검증은 skip
+
+        }
     }
 
     @DisplayName("POST /api/v1/user")
@@ -149,13 +153,10 @@ void returnUserResponse_whenSuccessful() {
                     .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
 
             var data = response.getBody().data();
-            assertThat(data).isNotNull();
-            assertThat(data.userId()).isEqualTo("ajchoi0928");
-            assertThat(data.userName()).isEqualTo("junho");
-            assertThat(data.email()).isEqualTo("loopers@loopers.com");
+            // 응답값과 실제 저장된 엔티티 검증
+            var savedUser = userJpaRepository.findByUserId("ajchoi0928").get();
+            assertThatSameValue(data, savedUser);
 
-            // DB 검증
-            assertThat(userJpaRepository.findByUserId("ajchoi0928")).isPresent();
         }
 
         @DisplayName("회원가입 시 성별 정보가 없는 경우 '400 Bad Reqeuset' 응답을 반환한다")
@@ -187,8 +188,6 @@ void throwsException_whenBlankGenderIsProvided() {
                     .isEqualTo(ApiResponse.Metadata.Result.FAIL);
             assertThat(response.getBody().meta().errorCode()).isNotBlank();
             assertThat(response.getBody().meta().message()).isNotBlank();
-
-
         }
 
 
@@ -199,4 +198,15 @@ private static HttpEntity<Object> json(Object body) {
         }
 
     }
+
+
+    private static void assertThatSameValue (UserV1Dto.UserResponse actual, UserModel entity) {
+        assertThat(actual).isNotNull();
+        assertThat(actual.userId()).isEqualTo(entity.getUserId());
+        assertThat(actual.userName()).isEqualTo(entity.getUserName());
+        assertThat(actual.description()).isEqualTo(entity.getDescription());
+        assertThat(actual.email()).isEqualTo(entity.getEmail());
+        assertThat(actual.birthDate()).isEqualTo(entity.getBirthDate());
+        assertThat(actual.gender()).isEqualTo(entity.getGender());
+    }
 }

From 106c36b44c998eea9033d31031176c674f5b4548 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 00:05:24 +0900
Subject: [PATCH 16/40] =?UTF-8?q?docs:=20=EC=A7=84=EC=B2=99=EC=83=81?=
 =?UTF-8?q?=ED=99=A9=20=EA=B4=80=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 회원가입 E2E 및 내 정보조회 통합테스트/E2E 테스트 완료
---
 .codeguide/loopers-1-week.md | 27 +++++++++++++++++++++------
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/.codeguide/loopers-1-week.md b/.codeguide/loopers-1-week.md
index 9821d259d..b8eb7e8e7 100644
--- a/.codeguide/loopers-1-week.md
+++ b/.codeguide/loopers-1-week.md
@@ -17,20 +17,20 @@
 
 **🌐 E2E 테스트**
 
-- [ ]  회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.
-- [ ]  회원 가입 시에 성별이 없을 경우, `400 Bad Request` 응답을 반환한다.
+- [x]  회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.
+- [x]  회원 가입 시에 성별이 없을 경우, `400 Bad Request` 응답을 반환한다.
 
 ### 내 정보 조회
 
 **🔗 통합 테스트**
 
-- [ ]  해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.
-- [ ]  해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
+- [x]  해당 ID 의 회원이 존재할 경우, 회원 정보가 반환된다.
+- [x]  해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
 
 **🌐 E2E 테스트**
 
-- [ ]  내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다.
-- [ ]  존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다.
+- [x]  내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다.
+- [x]  존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다.
 
 ### 포인트 조회
 
@@ -43,3 +43,18 @@
 
 - [ ]  포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다.
 - [ ]  `X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다.
+
+### 포인트 충전
+
+**🧱 단위 테스트**
+
+- [ ]  0 이하의 정수로 포인트를 충전 시 실패한다.
+
+**🔗 통합 테스트**
+
+- [ ]  존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패한다.
+
+**🌐 E2E 테스트**
+
+- [ ]  존재하는 유저가 1000원을 충전할 경우, 충전된 보유 총량을 응답으로 반환한다.
+- [ ]  존재하지 않는 유저로 요청할 경우, `404 Not Found` 응답을 반환한다.
\ No newline at end of file

From 318cf62235b021ceb18162a928b1ec5f2af65f6e Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 04:09:44 +0900
Subject: [PATCH 17/40] =?UTF-8?q?test:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20Mock=20=ED=85=8C?=
 =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환된다
- 해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다
---
 .../domain/user/UserServicePointTest.java     | 70 +++++++++++++++++++
 1 file changed, 70 insertions(+)
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
new file mode 100644
index 000000000..3802c5344
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
@@ -0,0 +1,70 @@
+package com.loopers.domain.user;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
+
+@ExtendWith(MockitoExtension.class)
+public class UserServicePointTest {
+    @Mock
+    UserRepository userRepository;
+
+    @InjectMocks
+    UserService userService;
+
+    private UserModel userModel;
+
+    @BeforeEach
+    void setUp() {
+        String userId = "ajchoi0928";
+        String userName = "junho";
+        String description = "loopers backend developer";
+        String email = "loopers@loopers.com";
+        String birthDate = "1997-09-28";
+        String gender = "M";
+        Integer points = 1000;
+        userModel = new UserModel(userId, userName, description, email, birthDate, gender, points);
+    }
+
+
+    @DisplayName("해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환된다")
+    @Test
+    void returnPoint_whenUserExists() {
+        // given
+        given(userRepository.findByUserId(userModel.getUserId()))
+                .willReturn(Optional.of(userModel));
+
+        // when
+        Integer points = userService.getUserOrNull(userModel.getUserId()).getPoint();
+
+        // then
+        // 상태와 행위검증
+        assertThat(points).isEqualTo(userModel.getPoint());
+        verify(userRepository).findByUserId(userModel.getUserId());
+    }
+
+    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다")
+    @Test
+    void returnNull_whenUserDoesNotExist() {
+        // given
+        given(userRepository.findByUserId(userModel.getUserId()))
+                .willReturn(Optional.empty());
+
+        // when
+        UserModel foundUser = userService.getUserOrNull(userModel.getUserId());
+
+        // then
+        assertThat(foundUser).isNull();
+        verify(userRepository).findByUserId(userModel.getUserId());
+    }
+}

From 5a337d8d0ca99a84d0b3d853ea74d2da34592527 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 04:22:10 +0900
Subject: [PATCH 18/40] =?UTF-8?q?test:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9?=
 =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
---
 .../user/UserPointIntegerationTest.java       | 71 +++++++++++++++++++
 1 file changed, 71 insertions(+)
 create mode 100644 apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java
new file mode 100644
index 000000000..98b3bc18b
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java
@@ -0,0 +1,71 @@
+package com.loopers.domain.user;
+
+import com.loopers.support.error.CoreException;
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@SpringBootTest
+public class UserPointIntegerationTest {
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private DatabaseCleanUp databaseCleanUp;
+
+    private UserModel userModel;
+
+    @BeforeEach
+    void setUp() {
+        String userId = "ajchoi0928";
+        String userName = "junho";
+        String description = "loopers backend developer";
+        String email = "loopers@loopers.com";
+        String birthDate = "1997-09-28";
+        String gender = "M";
+        Integer point = 1000;
+        userModel = new UserModel(userId, userName, description, email, birthDate, gender, point);
+    }
+
+    @AfterEach
+    void tearDown() {
+        databaseCleanUp.truncateAllTables();
+    }
+
+    @DisplayName("해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환된다")
+    @Test
+    void getUserPoint_whenUserExists() {
+        // given
+        userRepository.save(userModel);
+
+        // when
+        UserModel foundUser = userService.getUserOrNull(userModel.getUserId());
+
+        // then
+        assertThat(foundUser).isNotNull();
+        assertThat(foundUser.getPoint()).isEqualTo(userModel.getPoint());
+    }
+
+    @DisplayName("해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다")
+    @Test
+    void getUserPointNull_whenUserDoesNotExist() {
+        // given
+        userRepository.deleteUser(userModel.getUserId());
+
+        // when
+        UserModel foundUser = userService.getUserOrNull(userModel.getUserId());
+
+        // then
+        assertThat(foundUser).isNull();
+        assertThrows(NullPointerException.class, () -> foundUser.getPoint());
+    }
+}

From c2449855219a2e1058ebcd936f6702f149b458e3 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 05:08:44 +0900
Subject: [PATCH 19/40] =?UTF-8?q?feat:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20API=20=EB=AA=85?=
 =?UTF-8?q?=EC=84=B8=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/loopers/application/user/UserFacade.java   | 14 ++++++++++----
 .../java/com/loopers/domain/user/UserService.java  |  7 +++++++
 .../loopers/interfaces/api/user/UserV1ApiSpec.java | 12 ++++++++++--
 .../interfaces/api/user/UserV1Controller.java      |  7 +++++++
 4 files changed, 34 insertions(+), 6 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 4ed7e53e2..4c833c569 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -4,9 +4,9 @@
 import com.loopers.domain.user.UserService;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import jakarta.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @RequiredArgsConstructor
 @Component
@@ -15,23 +15,29 @@ public class UserFacade {
 
     /**
      * 사용자의 회원가입과 관련된 일련의 비즈니스 로직을 처리한다
+     *
      * @param newUser
      * @return
      */
     @Transactional
     public UserInfo joinUser(UserCommand.Create newUser) {
         UserModel resultUser = userService.createUser(newUser); // 실제 domain 계층 처리 결과
-        UserInfo userInfo =UserInfo.from(resultUser); // application 레이어용 불변 객체
+        UserInfo userInfo = UserInfo.from(resultUser); // application 레이어용 불변 객체
         // do sth
         return userInfo;
     }
 
-    @Transactional
+    @Transactional(readOnly = true)
     public UserInfo getUserInfo(String userId) {
         UserModel model = userService.getUserOrNull(userId);
-        if(model == null) {
+        if (model == null) {
             throw new CoreException(ErrorType.NOT_FOUND, "이용자ID를 확인해주세요");
         }
         return UserInfo.from(model);
     }
+
+    @Transactional(readOnly = true)
+    public Integer getUserPoint(String userId) {
+        return userService.getUserPoint(userId);
+    }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index a2d9f6d65..5b5caa149 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -29,4 +29,11 @@ public UserModel createUser(UserCommand.Create newUser) {
 
         return userRepository.save(newUser.toModel());
     }
+
+    @Transactional(readOnly = true)
+    public Integer getUserPoint(String userId) {
+        return userRepository.findByUserId(userId)
+                .map(UserModel::getPoint)
+                .orElse(null);
+    }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 4b96eab6e..5bc1a438d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -39,6 +39,14 @@ ApiResponse<UserV1Dto.UserResponse> getUser (
             @PathVariable String userId
     );
 
-
-
+    @Operation(
+            summary = "포인트 조회",
+            description = "userId에 대한 현재 포인트를 조회합니다."
+    )
+    ApiResponse<Integer> getUserPoint (
+            @Schema(name = "userId",
+                    description = "조회할 예시의 ID",
+                    example = "ajchoi0928")
+            @PathVariable String userId
+    );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index 3515bff63..8ba3fdf02 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -31,4 +31,11 @@ public ApiResponse<UserV1Dto.UserResponse> getUser(@PathVariable("userId") Strin
         UserInfo userInfo = userFacade.getUserInfo(userId);
         return ApiResponse.success(UserV1Dto.UserResponse.from(userInfo));
     }
+
+    @GetMapping("/{userId}/point")
+    @Override
+    public ApiResponse<Integer> getUserPoint(@PathVariable("userId") String userId) {
+        Integer userPoint = userFacade.getUserPoint(userId);
+        return ApiResponse.success(userPoint);
+    }
 }

From f026321c1983ff03c5a153079f913b052e587435 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 05:08:59 +0900
Subject: [PATCH 20/40] =?UTF-8?q?test:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B4=80=EB=A0=A8=20E2E=20=ED=85=8C?=
 =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../interfaces/api/UserV1ApiE2ETest.java      | 83 ++++++++++++++++---
 1 file changed, 71 insertions(+), 12 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index 99b37199b..b24e005c7 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -4,10 +4,7 @@
 import com.loopers.infrastructure.user.UserJpaRepository;
 import com.loopers.interfaces.api.user.UserV1Dto;
 import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.web.client.TestRestTemplate;
@@ -23,6 +20,7 @@ class UserV1ApiE2ETest {
 
     private static final Function<String, String> ENDPOINT_GETUSER = id -> "/api/v1/user/" + id;
     private static final String ENDPOINT_SIGNUP = "/api/v1/user";
+    private static final Function<String, String> ENDPOINT_GETUSER_POINT = id -> "/api/v1/user/" + id + "/point";
 
     private final TestRestTemplate testRestTemplate;
     private final UserJpaRepository userJpaRepository;
@@ -57,22 +55,34 @@ void tearDown() {
     @DisplayName("GET /api/v1/user/{id}")
     @Nested
     class Get {
-        @DisplayName("내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다")
-        @Test
-        void returnsUserInfo_whenValidUserIdIsProvided() {
-            // given
+
+        private UserModel someUserModel;
+
+        /**
+         * 조회용 E2E 클래스에 대한 픽스처
+         */
+        @BeforeEach
+        void setUp() {
             String userId = "ajchoi0928";
             String userName = "junho";
             String description = "loopers backend developer";
             String email = "loopers@loopers.com";
             String birthDate = "1997-09-28";
             String gender = "M";
-            UserModel userModel = new UserModel(userId, userName, description, email, birthDate, gender);
-            userJpaRepository.save(userModel);
+            Integer point = 50;
+            someUserModel = new UserModel(userId, userName, description, email, birthDate, gender, point);
+        }
+
+
+        @DisplayName("내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다")
+        @Test
+        void returnsUserInfo_whenValidUserIdIsProvided() {
+            // given
+            userJpaRepository.save(someUserModel);
 
             //when
             ResponseEntity<ApiResponse<UserV1Dto.UserResponse>> response =
-                    testRestTemplate.exchange(ENDPOINT_GETUSER.apply(userId),
+                    testRestTemplate.exchange(ENDPOINT_GETUSER.apply(someUserModel.getUserId()),
                             HttpMethod.GET,
                             new HttpEntity<>(null),
                             new ParameterizedTypeReference<>() {
@@ -86,7 +96,7 @@ void returnsUserInfo_whenValidUserIdIsProvided() {
                     .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
 
             var data = response.getBody().data();
-            assertThatSameValue(data, userModel);
+            assertThatSameValue(data, someUserModel);
         }
 
         @DisplayName("존재하지 않는 ID 로 조회할 경우, `404 Not Found` 응답을 반환한다")
@@ -116,6 +126,55 @@ void throwsNotFoundException_whenInvalidUserIdIsProvided() {
             // 에러메시지 내용 검증은 skip
 
         }
+
+        @DisplayName("포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다")
+        @Test
+        void returnUserPoint_whenValidUserIdIsProvided() {
+            // given
+            userJpaRepository.save(someUserModel);
+            HttpHeaders headers = new HttpHeaders();
+            headers.add("X-USER-ID", someUserModel.getUserId());
+
+            //when
+            ResponseEntity<ApiResponse<Integer>> response =
+                    testRestTemplate.exchange(ENDPOINT_GETUSER_POINT.apply(someUserModel.getUserId()),
+                            HttpMethod.GET,
+                            new HttpEntity<>(headers),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
+
+            assertThat(response.getBody().data()).isEqualTo(someUserModel.getPoint());
+        }
+
+        @DisplayName("`X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다")
+        @Test
+        void throwsBadRequestException_whenHeaderUserIdIsMissing() {
+            // given
+            userJpaRepository.save(someUserModel);
+
+            // when
+            ResponseEntity<ApiResponse<Integer>> response =
+                    testRestTemplate.exchange(ENDPOINT_GETUSER_POINT.apply(someUserModel.getUserId()),
+                            HttpMethod.GET,
+                            new HttpEntity<>(null),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.FAIL);
+            assertThat(response.getBody().meta().errorCode()).isNotBlank();
+        }
     }
 
     @DisplayName("POST /api/v1/user")

From 41f1f2e8a8beb73122ed10bc5dd0f1c306c71ef5 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 05:23:55 +0900
Subject: [PATCH 21/40] =?UTF-8?q?refactor:=20`X-USER-ID`=20=ED=97=A4?=
 =?UTF-8?q?=EB=8D=94=20=ED=95=84=EC=88=98=EA=B0=92=20=EC=A7=80=EC=A0=95=20?=
 =?UTF-8?q?=EB=B0=8F=20ApiControllerAdvice=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

handleBadRequest() 오버로딩
- 필수 헤더 누락시, HTTP Internal Error 가 아닌 HTTP Bad Request가 응답되도록
---
 .../interfaces/api/ApiControllerAdvice.java       |  8 ++++++++
 .../interfaces/api/user/UserV1ApiSpec.java        |  6 ++++--
 .../interfaces/api/user/UserV1Controller.java     | 15 ++++++++++++++-
 3 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java
index 20b2809c8..3e8399d17 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/ApiControllerAdvice.java
@@ -8,6 +8,7 @@
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.MissingRequestHeaderException;
 import org.springframework.web.bind.MissingServletRequestParameterException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -46,6 +47,13 @@ public ResponseEntity<ApiResponse<?>> handleBadRequest(MissingServletRequestPara
         return failureResponse(ErrorType.BAD_REQUEST, message);
     }
 
+    @ExceptionHandler
+    public ResponseEntity<ApiResponse<?>> handleBadRequest(MissingRequestHeaderException e) {
+        String name = e.getHeaderName();
+        String message = String.format("필수 헤더 파라미터 '%s' 가 누락되었습니다.", name);
+        return failureResponse(ErrorType.BAD_REQUEST, message);
+    }
+
     @ExceptionHandler
     public ResponseEntity<ApiResponse<?>> handleBadRequest(HttpMessageNotReadableException e) {
         String errorMessage;
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 5bc1a438d..2287aa7db 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -5,6 +5,7 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
 
 @Tag(name = "Example V1 API", description = "Loopers 예시 API 입니다.")
 public interface UserV1ApiSpec {
@@ -47,6 +48,7 @@ ApiResponse<Integer> getUserPoint (
             @Schema(name = "userId",
                     description = "조회할 예시의 ID",
                     example = "ajchoi0928")
-            @PathVariable String userId
-    );
+            @PathVariable String userId,
+            @RequestHeader String xUserId
+            );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index 8ba3fdf02..a94113519 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -4,6 +4,8 @@
 import com.loopers.application.user.UserFacade;
 import com.loopers.application.user.UserInfo;
 import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
@@ -34,7 +36,18 @@ public ApiResponse<UserV1Dto.UserResponse> getUser(@PathVariable("userId") Strin
 
     @GetMapping("/{userId}/point")
     @Override
-    public ApiResponse<Integer> getUserPoint(@PathVariable("userId") String userId) {
+    public ApiResponse<Integer> getUserPoint(
+            @PathVariable("userId") String userId,
+            @RequestHeader(value = "X-USER-ID", required = true) String xUserId) {
+
+        if(xUserId == null || xUserId.isEmpty()) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "X-USER-ID is required");
+        }
+
+        if(!xUserId.equals(userId)) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "X-USER-ID and user ID do not match");
+        }
+
         Integer userPoint = userFacade.getUserPoint(userId);
         return ApiResponse.success(userPoint);
     }

From 3ba2704f16d47b6b34f7c28015d89b8dbee5ad94 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 05:24:32 +0900
Subject: [PATCH 22/40] =?UTF-8?q?docs:=20`X-USER-ID`=20=ED=97=A4=EB=8D=94?=
 =?UTF-8?q?=20=ED=95=84=EC=88=98=EA=B0=92=20=EC=A7=80=EC=A0=95=20=EB=B0=8F?=
 =?UTF-8?q?=20ApiControllerAdvice=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

** 포인트조회 - E2E 테스트**

- 포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다.
- `X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다.
---
 .codeguide/loopers-1-week.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.codeguide/loopers-1-week.md b/.codeguide/loopers-1-week.md
index b8eb7e8e7..852a6a98e 100644
--- a/.codeguide/loopers-1-week.md
+++ b/.codeguide/loopers-1-week.md
@@ -36,13 +36,13 @@
 
 **🔗 통합 테스트**
 
-- [ ]  해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환된다.
-- [ ]  해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
+- [x]  해당 ID 의 회원이 존재할 경우, 보유 포인트가 반환된다.
+- [x]  해당 ID 의 회원이 존재하지 않을 경우, null 이 반환된다.
 
 **🌐 E2E 테스트**
 
-- [ ]  포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다.
-- [ ]  `X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다.
+- [x]  포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다.
+- [x]  `X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다.
 
 ### 포인트 충전
 

From ab806ce2471a3a7a0573a875fd23b83aab51534b Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 06:36:46 +0900
Subject: [PATCH 23/40] =?UTF-8?q?test=20:=200=20=EC=9D=B4=ED=95=98=20?=
 =?UTF-8?q?=EC=A0=95=EC=88=98=EB=A1=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=B6=A9=EC=A0=84=20=EC=8B=9C=20=EC=8B=A4=ED=8C=A8=20=ED=85=8C?=
 =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=84=A4=EA=B3=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../loopers/domain/user/UserModelTest.java    | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index 154d1bd2c..eab4a46a4 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -224,6 +224,27 @@ void throwsBadRequestException_whenGenderIsBlank() {
             assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
         }
 
+        @DisplayName("0 이하의 정수로 포인트를 충전 시 실패한다")
+        @Test
+        void throwBadRequestException_whenPointIsNegative() {
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "";
+            String gender = "M";
+            Integer point = -1;
+
+            // when
+            CoreException result = assertThrows(CoreException.class, () -> {
+                new UserModel(userId, userName, description, email, birthDate, gender, point);
+            });
+
+            // then
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
+
 
     }
 }

From 520052142a02154d8af58a09a680c33c96b17336 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 06:41:46 +0900
Subject: [PATCH 24/40] =?UTF-8?q?feat:=20User=EC=97=94=ED=8B=B0=ED=8B=B0?=
 =?UTF-8?q?=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B6=A9=EC=A0=84=20=EA=B5=AC?=
 =?UTF-8?q?=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/java/com/loopers/domain/user/UserModel.java  | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 7f50ed5f7..6b7995218 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -56,6 +56,11 @@ public void updateDescription(String newDescription) {
         this.description = newDescription;
     }
 
+    public void updatePoint(Integer newPoint) {
+        validatePoint(newPoint);
+        this.point +=  newPoint;
+    }
+
 
     private void validateNotBlank(String field, String message) {
         if (field == null || field.isBlank()) {
@@ -98,6 +103,12 @@ private void validateBirthDate(String birthDate) {
         }
     }
 
+    private void validatePoint(Integer point) {
+        if (point == null || point <= 0) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "충전 포인트는 0 이하일 수 없습니다.");
+        }
+    }
+
     private void validateGender(String gender) {
         validateNotBlank(gender, "성별은 비어있을 수 없습니다.");
     }

From 537d70cb22368cc1e500c0155857c4b5c1373218 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 06:42:18 +0900
Subject: [PATCH 25/40] =?UTF-8?q?test:=20UserEntity=20=ED=8F=AC=EC=9D=B8?=
 =?UTF-8?q?=ED=8A=B8=20=EC=B6=A9=EC=A0=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?=
 =?UTF-8?q?=EC=99=84=EB=A3=8C=20-=200=20=EC=9D=B4=ED=95=98=EC=9D=98=20?=
 =?UTF-8?q?=EC=A0=95=EC=88=98=EB=A1=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=EB=A5=BC=20=EC=B6=A9=EC=A0=84=20=EC=8B=9C=20=EC=8B=A4=ED=8C=A8?=
 =?UTF-8?q?=ED=95=9C=EB=8B=A4.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../test/java/com/loopers/domain/user/UserModelTest.java   | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index eab4a46a4..21d1ccaa1 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -232,13 +232,14 @@ void throwBadRequestException_whenPointIsNegative() {
             String userName = "junho";
             String description = "loopers backend developer";
             String email = "ajchoi0928@loopers.com";
-            String birthDate = "";
+            String birthDate = "1997-09-28";
             String gender = "M";
-            Integer point = -1;
+            Integer point = 0;
 
             // when
+            UserModel userModel = new UserModel(userId, userName, description, email, birthDate, gender, point);
             CoreException result = assertThrows(CoreException.class, () -> {
-                new UserModel(userId, userName, description, email, birthDate, gender, point);
+                userModel.updatePoint(-1);
             });
 
             // then

From bd6886f7a63f2132c00d87b549d36eccc801f48a Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:01:19 +0900
Subject: [PATCH 26/40] =?UTF-8?q?test:=20UserService=20=ED=8F=AC=EC=9D=B8?=
 =?UTF-8?q?=ED=8A=B8=20=EC=B6=A9=EC=A0=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?=
 =?UTF-8?q?=EC=84=A4=EA=B3=84=20-=200=20=EC=9D=B4=ED=95=98=EC=9D=98=20?=
 =?UTF-8?q?=EC=A0=95=EC=88=98=EB=A1=9C=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=EB=A5=BC=20=EC=B6=A9=EC=A0=84=20=EC=8B=9C=20=EC=8B=A4=ED=8C=A8?=
 =?UTF-8?q?=ED=95=9C=EB=8B=A4.=20-=20=EC=9C=A0=ED=9A=A8=ED=95=9C=20?=
 =?UTF-8?q?=EC=9D=B4=EC=9A=A9=EC=9E=90=20ID=EC=97=90=20=EB=8C=80=ED=95=9C?=
 =?UTF-8?q?=20=EC=B6=A9=EC=A0=84=20=EC=8B=9C=EB=8F=84=EC=8B=9C,=20?=
 =?UTF-8?q?=EC=84=B1=EA=B3=B5=ED=95=9C=EB=8B=A4.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../com/loopers/domain/user/UserService.java  |  5 ++
 .../domain/user/UserServicePointTest.java     | 47 +++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 5b5caa149..e9713de35 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -36,4 +36,9 @@ public Integer getUserPoint(String userId) {
                 .map(UserModel::getPoint)
                 .orElse(null);
     }
+
+    // TODO
+    public Integer chargePoint(String userId, Integer point) {
+        return 0;
+    }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
index 3802c5344..6f6ac610e 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
@@ -1,5 +1,7 @@
 package com.loopers.domain.user;
 
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
@@ -11,7 +13,10 @@
 import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 @ExtendWith(MockitoExtension.class)
@@ -67,4 +72,46 @@ void returnNull_whenUserDoesNotExist() {
         assertThat(foundUser).isNull();
         verify(userRepository).findByUserId(userModel.getUserId());
     }
+
+    @DisplayName("존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패한다.")
+    @Test
+    void throwBadRequestException_whenChargePointUserDoesNotExist() {
+        // given
+        String userId = "unknown";
+        int point = 500;
+        given(userRepository.findByUserId(userId))
+                .willReturn(Optional.empty());
+
+        // when
+        CoreException ex = assertThrows(CoreException.class,
+                () -> userService.chargePoint(userId, point));
+
+        // then
+        assertThat(ex.getErrorType()).isEqualTo(ErrorType.NOT_FOUND); // 상태
+        verify(userRepository).findByUserId(userId); // 행위
+        verify(userRepository, never()).save(any()); //  행위
+    }
+
+    @DisplayName("유효한 이용자 ID에 대한 충전 시도시, 성공한다")
+    @Test
+    void doChargePoint_whenUserExists() {
+        // given
+        String userId = userModel.getUserId();
+        int point = 500;
+        given(userRepository.findByUserId(userId))
+                .willReturn(Optional.of(userModel));
+
+        // when
+        Integer afterPoint = userService.chargePoint(userId, point);
+
+        // then
+        UserModel afterUser = userRepository.findByUserId(userId).get();
+        assertThat(afterPoint).isEqualTo(userModel.getPoint() + point); // 상태
+        assertThat(afterPoint).isEqualTo(afterUser.getPoint());
+
+        verify(userRepository).findByUserId(userId); // 행위
+        verify(userRepository).save(userModel); //  행위
+    }
+
+
 }

From 004ee0616dc42a4bdb7ffeab2f10975c08e79949 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:13:43 +0900
Subject: [PATCH 27/40] =?UTF-8?q?feat:=20UserService=20=ED=8F=AC=EC=9D=B8?=
 =?UTF-8?q?=ED=8A=B8=20=EC=B6=A9=EC=A0=84=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?=
 =?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../main/java/com/loopers/domain/user/UserService.java    | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index e9713de35..01c2c0468 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -37,8 +37,12 @@ public Integer getUserPoint(String userId) {
                 .orElse(null);
     }
 
-    // TODO
     public Integer chargePoint(String userId, Integer point) {
-        return 0;
+        UserModel user = userRepository.findByUserId(userId)
+                .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "이용자ID를 확인해주세요."));
+        user.updatePoint(point);
+        userRepository.save(user);
+
+        return user.getPoint();
     }
 }

From d159ef0a65196c4e31d9bf250e8de4661620600a Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:14:45 +0900
Subject: [PATCH 28/40] =?UTF-8?q?test:=20UserService=20=ED=8F=AC=EC=9D=B8?=
 =?UTF-8?q?=ED=8A=B8=20=ED=86=B5=ED=95=A9=ED=85=8C=EC=8A=A4=ED=8A=B8=20?=
 =?UTF-8?q?=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패한다.
- 유효한 이용자 ID에 대한 충전 시도시, 성공한다
---
 .../com/loopers/domain/user/UserServicePointTest.java    | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
index 6f6ac610e..265d8354d 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
@@ -16,8 +16,7 @@
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
 
 @ExtendWith(MockitoExtension.class)
 public class UserServicePointTest {
@@ -106,11 +105,11 @@ void doChargePoint_whenUserExists() {
 
         // then
         UserModel afterUser = userRepository.findByUserId(userId).get();
-        assertThat(afterPoint).isEqualTo(userModel.getPoint() + point); // 상태
+        assertThat(afterPoint).isEqualTo(userModel.getPoint()); // 상태
         assertThat(afterPoint).isEqualTo(afterUser.getPoint());
 
-        verify(userRepository).findByUserId(userId); // 행위
-        verify(userRepository).save(userModel); //  행위
+        verify(userRepository, atLeastOnce()).findByUserId(userId); // 행위
+        verify(userRepository, times(1)).save(userModel); //  행위
     }
 
 

From e05651a596337ec52cc3748eafc962ded2e3d36d Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:41:07 +0900
Subject: [PATCH 29/40] =?UTF-8?q?feat:=20UserV1=20API=20=EB=AA=85=EC=84=B8?=
 =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?=
 =?UTF-8?q?=20=EC=B6=A9=EC=A0=84=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/com/loopers/application/user/UserFacade.java |  5 +++++
 .../loopers/interfaces/api/user/UserV1ApiSpec.java    | 11 +++++++++++
 .../loopers/interfaces/api/user/UserV1Controller.java | 11 +++++++++++
 .../com/loopers/interfaces/api/user/UserV1Dto.java    |  6 ++++++
 4 files changed, 33 insertions(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 4c833c569..511c06509 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -40,4 +40,9 @@ public UserInfo getUserInfo(String userId) {
     public Integer getUserPoint(String userId) {
         return userService.getUserPoint(userId);
     }
+
+    @Transactional
+    public Integer chargeUserPoint(String userId, Integer point) {
+        return userService.chargePoint(userId, point);
+    }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
index 2287aa7db..9e1e7e373 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1ApiSpec.java
@@ -51,4 +51,15 @@ ApiResponse<Integer> getUserPoint (
             @PathVariable String userId,
             @RequestHeader String xUserId
             );
+
+    @Operation(
+            summary = "포인트 충전",
+            description = "UserV1Dto.UserPointChargeRequest 포맷으로 포인트 충전을 처리합니다"
+    )
+    ApiResponse<Integer> chargeUserPoint (
+            @Schema(name = "",
+                    description = "이용자ID, 충전금액",
+                    example = "ajchoi0928, 500")
+            UserV1Dto.UserPointChargeRequest userCreateRequest
+    );
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
index a94113519..296bea28f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Controller.java
@@ -51,4 +51,15 @@ public ApiResponse<Integer> getUserPoint(
         Integer userPoint = userFacade.getUserPoint(userId);
         return ApiResponse.success(userPoint);
     }
+
+    @PutMapping("/chargePoint")
+    @Override
+    public ApiResponse<Integer> chargeUserPoint(
+            @RequestBody @Valid UserV1Dto.UserPointChargeRequest request) {
+        String userId = request.userId();
+        Integer newPoint = request.point();
+        Integer totalPoint = userFacade.chargeUserPoint(userId, newPoint);
+
+        return ApiResponse.success(totalPoint);
+    }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
index fe4ab151c..ba23f98ac 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
@@ -15,6 +15,7 @@ public record UserCreateRequest(
     ) {
         /**
          * interfaces -> application 의존 OK(안쪽 의존)
+         *
          * @return
          */
         public UserCommand.Create toCommand() {
@@ -22,6 +23,11 @@ public UserCommand.Create toCommand() {
         }
     }
 
+    public record UserPointChargeRequest(
+            String userId,
+            Integer point
+    ) {
+    }
 
     public record UserResponse(
             String userId,

From e094ed856a3cf0d440bfd50eee497be14a884512 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:43:17 +0900
Subject: [PATCH 30/40] =?UTF-8?q?test:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=B6=A9=EC=A0=84=20-=20E2E=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?=
 =?UTF-8?q?=EC=99=84=EB=A3=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- 존재하는 유저가 1000원을 충전할 경우, 충전된 보유 총량을 응답으로 반환한다.
- 존재하지 않는 유저로 요청할 경우, `404 Not Found` 응답을 반환한다.
---
 .../interfaces/api/UserV1ApiE2ETest.java      | 89 ++++++++++++++++++-
 1 file changed, 85 insertions(+), 4 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index b24e005c7..f5390638b 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -20,6 +20,7 @@ class UserV1ApiE2ETest {
 
     private static final Function<String, String> ENDPOINT_GETUSER = id -> "/api/v1/user/" + id;
     private static final String ENDPOINT_SIGNUP = "/api/v1/user";
+    private static final String ENDPOINT_CHARGEPOINT = "/api/v1/user/chargePoint";
     private static final Function<String, String> ENDPOINT_GETUSER_POINT = id -> "/api/v1/user/" + id + "/point";
 
     private final TestRestTemplate testRestTemplate;
@@ -248,12 +249,86 @@ void throwsException_whenBlankGenderIsProvided() {
             assertThat(response.getBody().meta().errorCode()).isNotBlank();
             assertThat(response.getBody().meta().message()).isNotBlank();
         }
+    }
+
 
+    @DisplayName("PUT /api/v1/user")
+    @Nested
+    class Put {
+        private UserModel someUserModel;
 
-        private static HttpEntity<Object> json(Object body) {
-            HttpHeaders h = new HttpHeaders();
-            h.setContentType(MediaType.APPLICATION_JSON);
-            return new HttpEntity<>(body, h);
+        /**
+         * 조회용 E2E 클래스에 대한 픽스처
+         */
+        @BeforeEach
+        void setUp() {
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "loopers@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+            Integer point = 50;
+            someUserModel = new UserModel(userId, userName, description, email, birthDate, gender, point);
+        }
+
+        @DisplayName("존재하는 유저가 충전할 경우 충전된 보유 총량을 응답으로 반환하다")
+        @Test
+        public void returnUserTotalPoint_whenSuccessful() {
+            // given
+            userJpaRepository.save(someUserModel);
+            int point = 1000;
+            UserV1Dto.UserPointChargeRequest request = new UserV1Dto.UserPointChargeRequest(
+                    someUserModel.getUserId(), point
+            );
+
+            // when
+            ResponseEntity<ApiResponse<Integer>> response =
+                    testRestTemplate.exchange(
+                            ENDPOINT_CHARGEPOINT,
+                            HttpMethod.PUT,
+                            json(request),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.SUCCESS);
+
+            var data = response.getBody().data();
+            // 응답값과 실제 저장된 엔티티 검증
+            var savedUser = userJpaRepository.findByUserId(someUserModel.getUserId()).get();
+            assertThat(data).isEqualTo(savedUser.getPoint());
+        }
+
+        @DisplayName("존재하지 않는 유저로 요청할 경우, `404 Not Found` 응답을 반환한다.")
+        @Test
+        void throwsException_whenUserNotFound() {
+            // given
+            int point = 1000;
+            UserV1Dto.UserPointChargeRequest request = new UserV1Dto.UserPointChargeRequest(
+                    someUserModel.getUserId(), point
+            );
+            userJpaRepository.deleteByUserId(request.userId());
+
+            // when
+            ResponseEntity<ApiResponse<Integer>> response =
+                    testRestTemplate.exchange(
+                            ENDPOINT_CHARGEPOINT,
+                            HttpMethod.PUT,
+                            json(request),
+                            new ParameterizedTypeReference<>() {
+                            }
+                    );
+
+            // then
+            assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+            assertThat(response.getBody()).isNotNull();
+            assertThat(response.getBody().meta().result())
+                    .isEqualTo(ApiResponse.Metadata.Result.FAIL);
         }
 
     }
@@ -268,4 +343,10 @@ private static void assertThatSameValue (UserV1Dto.UserResponse actual, UserMode
         assertThat(actual.birthDate()).isEqualTo(entity.getBirthDate());
         assertThat(actual.gender()).isEqualTo(entity.getGender());
     }
+
+    private static HttpEntity<Object> json(Object body) {
+        HttpHeaders h = new HttpHeaders();
+        h.setContentType(MediaType.APPLICATION_JSON);
+        return new HttpEntity<>(body, h);
+    }
 }

From 98c4e4b65a3f0dd4a846328f4a35bee4f3a12124 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Fri, 31 Oct 2025 07:43:45 +0900
Subject: [PATCH 31/40] =?UTF-8?q?docs:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?=
 =?UTF-8?q?=EC=B6=A9=EC=A0=84=20=EA=B3=BC=EC=A0=9C=20=EC=99=84=EB=A3=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .codeguide/loopers-1-week.md | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.codeguide/loopers-1-week.md b/.codeguide/loopers-1-week.md
index 852a6a98e..e5a7bc427 100644
--- a/.codeguide/loopers-1-week.md
+++ b/.codeguide/loopers-1-week.md
@@ -48,13 +48,13 @@
 
 **🧱 단위 테스트**
 
-- [ ]  0 이하의 정수로 포인트를 충전 시 실패한다.
+- [x]  0 이하의 정수로 포인트를 충전 시 실패한다.
 
 **🔗 통합 테스트**
 
-- [ ]  존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패한다.
+- [x]  존재하지 않는 유저 ID 로 충전을 시도한 경우, 실패한다.
 
 **🌐 E2E 테스트**
 
-- [ ]  존재하는 유저가 1000원을 충전할 경우, 충전된 보유 총량을 응답으로 반환한다.
-- [ ]  존재하지 않는 유저로 요청할 경우, `404 Not Found` 응답을 반환한다.
\ No newline at end of file
+- [x]  존재하는 유저가 1000원을 충전할 경우, 충전된 보유 총량을 응답으로 반환한다.
+- [x]  존재하지 않는 유저로 요청할 경우, `404 Not Found` 응답을 반환한다.
\ No newline at end of file

From 6c31a07ca70d68f9dbc88a449563ee643e235e71 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 14:42:01 +0900
Subject: [PATCH 32/40] =?UTF-8?q?fix:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?=
 =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20point=EB=A5=BC=20?=
 =?UTF-8?q?=EC=A0=9C=EA=B1=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### 의도
application 레이어에서 domain 레이어로 회원가입과 관련된 데이터를 전달할때, 'point 는 도메인 레이어에서 정의된 초기값 0원으로 가입시킨다' 의미를 담으려 했었음

### 문제점
1) 회원가입시 프로모션 등으로 초기 포인트 등을 부여를 할수도 있음
2) Create 레코드와 UserModel 간 미사용 파라미터로 명확하지 않았음

### 수정
point 파라미터 추가로 일관성 적용
---
 .../java/com/loopers/application/user/UserCommand.java   | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java
index 9e4b45989..f8416e03d 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserCommand.java
@@ -16,7 +16,14 @@ public record Create (
             Integer point
     ) {
         public UserModel toModel() {
-            return new UserModel(userId, userName, description, email, birthDate, gender);
+            return new UserModel(
+                    userId,
+                    userName,
+                    description,
+                    email,
+                    birthDate,
+                    gender,
+                    point);
         }
     }
 }

From c9589306326cc10a4f70f68afcd9e83e5118681e Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 14:43:46 +0900
Subject: [PATCH 33/40] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=EB=AA=85?=
 =?UTF-8?q?=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...intIntegerationTest.java => UserPointIntegrationTest.java} | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)
 rename apps/commerce-api/src/test/java/com/loopers/domain/user/{UserPointIntegerationTest.java => UserPointIntegrationTest.java} (92%)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegrationTest.java
similarity index 92%
rename from apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java
rename to apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegrationTest.java
index 98b3bc18b..aaf8f9c88 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegerationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserPointIntegrationTest.java
@@ -1,17 +1,15 @@
 package com.loopers.domain.user;
 
-import com.loopers.support.error.CoreException;
 import com.loopers.utils.DatabaseCleanUp;
 import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.bean.override.mockito.MockitoBean;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @SpringBootTest
-public class UserPointIntegerationTest {
+public class UserPointIntegrationTest {
 
     @Autowired
     private UserService userService;

From a9912d0a54f0a3e66d725bf8c73f8b1d4a7ae0fe Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 15:03:53 +0900
Subject: [PATCH 34/40] =?UTF-8?q?fix:=20UserModel=20point=20=ED=95=84?=
 =?UTF-8?q?=EB=93=9C=20=EA=B2=80=EC=A6=9D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

### 검토사항
- UserModel 생성자에 point 음수에 대한 입력값 검증 미존재

### 단위테스트 작성
- UserModelTest.java

### 코드 수정
- 신규 회원가입 시 / 회원가입 후 포인트 충전 시에 대한 입력값 검증에
- 논리적 오류가 있음 확인 후 수정

### 테스트 성공 확인
---
 .../com/loopers/domain/user/UserModel.java    | 11 ++++++++--
 .../loopers/domain/user/UserModelTest.java    | 21 +++++++++++++++++++
 2 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 6b7995218..8604acf3e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -35,6 +35,7 @@ public UserModel(String userId, String name, String description, String email
         validateEmail(email);
         validateBirthDate(birthDate);
         validateGender(gender);
+        validateInitPoint(point);
 
         this.userId = userId;
         this.userName = name;
@@ -57,7 +58,7 @@ public void updateDescription(String newDescription) {
     }
 
     public void updatePoint(Integer newPoint) {
-        validatePoint(newPoint);
+        validateChargePoint(newPoint);
         this.point +=  newPoint;
     }
 
@@ -103,7 +104,13 @@ private void validateBirthDate(String birthDate) {
         }
     }
 
-    private void validatePoint(Integer point) {
+    private void validateInitPoint(Integer point) {
+        if (point == null || point < 0) {
+            throw new CoreException(ErrorType.BAD_REQUEST, "신규 포인트는 음수일 수 없습니다.");
+        }
+    }
+
+    private void validateChargePoint(Integer point) {
         if (point == null || point <= 0) {
             throw new CoreException(ErrorType.BAD_REQUEST, "충전 포인트는 0 이하일 수 없습니다.");
         }
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
index 21d1ccaa1..481b9a9c7 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserModelTest.java
@@ -183,6 +183,7 @@ void throwsBadRequestException_whenBirthDateIsBlank() {
             // assert
             assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
         }
+
         // user_name, 성별,
         @DisplayName("회원 이름이 빈 경우 BAD_REQUEST를 반환한다")
         @Test
@@ -247,5 +248,25 @@ void throwBadRequestException_whenPointIsNegative() {
         }
 
 
+        @DisplayName("0 이하 정수 포인트를 갖는 회원가입을 하면 실패한다")
+        @Test
+        void throwBadRequestException_whenPointUnderZero() {
+            // given
+            String userId = "ajchoi0928";
+            String userName = "junho";
+            String description = "loopers backend developer";
+            String email = "ajchoi0928@loopers.com";
+            String birthDate = "1997-09-28";
+            String gender = "M";
+            Integer point = -1;
+
+            // when
+            CoreException result = assertThrows(CoreException.class, () ->
+                new UserModel(userId, userName, description, email, birthDate, gender, point)
+            );
+
+            // then
+            assertThat(result.getErrorType()).isEqualTo(ErrorType.BAD_REQUEST);
+        }
     }
 }

From 476403ef247f18ad53708990250b998c38212c70 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 15:07:45 +0900
Subject: [PATCH 35/40] =?UTF-8?q?fix:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?=
 =?UTF-8?q?=EC=9E=85=EB=A0=A5=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EC=8B=A4?=
 =?UTF-8?q?=ED=8C=A8=EC=8B=9C=20=EC=97=90=EB=9F=AC=EB=A9=94=EC=8B=9C?=
 =?UTF-8?q?=EC=A7=80=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

잘못된 복사-붙여넣기로 에러메시지 오기입으로 인한 수정
---
 .../src/main/java/com/loopers/domain/user/UserModel.java        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
index 8604acf3e..b0bee9da4 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserModel.java
@@ -90,7 +90,7 @@ private void validateEmail(String email) {
 
         String validEmailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
         if (!Pattern.matches(validEmailRegex, email)) {
-            throw new CoreException(ErrorType.BAD_REQUEST, "ID는 영문 및 숫자만 사용할 수 있습니다.");
+            throw new CoreException(ErrorType.BAD_REQUEST, "이메일 입력형식이 잘못되었습니다.");
 
         }
     }

From 02c74f4e4255db5f67421cbf25e2f000d539a8b5 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 15:26:44 +0900
Subject: [PATCH 36/40] =?UTF-8?q?fix:=20@Transactional=20=EB=88=84?=
 =?UTF-8?q?=EB=9D=BD=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- UserService.java 내 chargePoint()

### @Transactional 이 없을 때 발생 가능한 문제

1) Dirty Checking이 동작하지 않을 수 있음
- 변화가 있는 모든 엔티티에 대해 DB 반영 / 영속성 컨텍스트가 관리하는 엔티티들.
- 준영속성/비영속성인 엔티티는 제외

2) 원자성 보장 불가
3) 트랜잭션 경계 실종
4) 동시성 및 경합 문제
5) 예외 발생시 rollback 관련 문제
Spring 기본 정책
	•	RuntimeException → rollback
	•	CheckedException → commit
---
 .../src/main/java/com/loopers/domain/user/UserService.java       | 1 +
 1 file changed, 1 insertion(+)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 01c2c0468..819616cd3 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -37,6 +37,7 @@ public Integer getUserPoint(String userId) {
                 .orElse(null);
     }
 
+    @Transactional
     public Integer chargePoint(String userId, Integer point) {
         UserModel user = userRepository.findByUserId(userId)
                 .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "이용자ID를 확인해주세요."));

From 5a05ed11cc411f23ef7faa16da42bf1f5c1ef5b5 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 16:04:42 +0900
Subject: [PATCH 37/40] =?UTF-8?q?fix:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?=
 =?UTF-8?q?=EB=9F=AC=20=EB=A0=88=EC=9D=B4=EC=96=B4=20point=20=ED=95=84?=
 =?UTF-8?q?=EB=93=9C=20=EA=B4=80=EB=A0=A8=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

회원가입시 초기 포인트 정책 변경 등에 따라 필요할 수도 있음
---
 .../java/com/loopers/interfaces/api/user/UserV1Dto.java     | 5 +++--
 .../java/com/loopers/interfaces/api/UserV1ApiE2ETest.java   | 6 ++++--
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
index ba23f98ac..5f22ec7b7 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserV1Dto.java
@@ -11,7 +11,8 @@ public record UserCreateRequest(
             String description,
             String email,
             String birthDate,
-            String gender
+            String gender,
+            Integer point
     ) {
         /**
          * interfaces -> application 의존 OK(안쪽 의존)
@@ -19,7 +20,7 @@ public record UserCreateRequest(
          * @return
          */
         public UserCommand.Create toCommand() {
-            return new UserCommand.Create(userId, userName, description, email, birthDate, gender, 0);
+            return new UserCommand.Create(userId, userName, description, email, birthDate, gender, point);
         }
     }
 
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
index f5390638b..7dd3a4af0 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/UserV1ApiE2ETest.java
@@ -192,7 +192,8 @@ void returnUserResponse_whenSuccessful() {
                     "loopers backend developer",
                     "loopers@loopers.com",
                     "1997-09-28",
-                    "M"
+                    "M",
+                    0
             );
 
             // var json = objectMapper.writeValueAsString(request);
@@ -229,7 +230,8 @@ void throwsException_whenBlankGenderIsProvided() {
                     "loopers backend developer",
                     "loopers@loopers.com",
                     "1997-09-28",
-                    ""
+                    "",
+                    0
             );
 
             // when

From 75689b88377a462add02f1c7919eb509528730a9 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 16:17:18 +0900
Subject: [PATCH 38/40] =?UTF-8?q?refactor:=20=EC=8B=A0=EA=B7=9C=ED=9A=8C?=
 =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8?=
 =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=20@BeforeEach=20=ED=94=BD?=
 =?UTF-8?q?=EC=8A=A4=EC=B2=98=20=EC=A0=81=EC=9A=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../user/UserServiceIntegrationTest.java      | 76 ++++++++-----------
 1 file changed, 32 insertions(+), 44 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index a3eb461d8..93098eb41 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -4,10 +4,7 @@
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -29,9 +26,6 @@ class UserServiceIntegrationTest {
     @Autowired
     private UserRepository userRepository;
 
-    /*@MockitoBean
-    private UserRepository userRepositoryMock;*/
-
     @Autowired
     private DatabaseCleanUp databaseCleanUp;
 
@@ -53,22 +47,37 @@ void tearDown() {
     @Nested
     class CreateUser {
 
+        private String userId;
+        private String userName;
+        private String description;
+        private String email;
+        private String birthDate;
+        private String gender;
+        private Integer newUserPointPolicy;
+        private UserCommand.Create createCommand;
+
+        @BeforeEach
+        void setUp() {
+            userId = "ajchoi0928";
+            userName = "junho";
+            description = "loopers backend developer";
+            email = "loopers@loopers.com";
+            birthDate = "1997-09-28";
+            gender = "M";
+            newUserPointPolicy = 0;
+            createCommand = new UserCommand.Create(userId, userName, description,
+                    email, birthDate, gender, newUserPointPolicy);
+        }
+
+
         @DisplayName("유효한 입력값을 갖는 신규 회원 정보로 회원가입이 수행된다")
         @Test
         void saveUser_whenAllRequiredFieldsAreProvidedAndValid() {
             // given
-            String userId = "ajchoi0928";
-            String userName = "junho";
-            String description = "loopers backend developer";
-            String email = "loopers@loopers.com";
-            String birthDate = "1997-09-28";
-            String gender = "M";
-
-            UserCommand.Create create = new UserCommand.Create(userId, userName
-                    , description, email, birthDate, gender, 0);
+            UserCommand.Create newUserCreate = createCommand;
 
             // when
-            UserModel result = userService.createUser(create);
+            UserModel result = userService.createUser(newUserCreate);
             int defaultPoint = 0;
 
             // then
@@ -93,21 +102,12 @@ void saveUser_whenAllRequiredFieldsAreProvidedAndValid() {
         @Test
         void throwsConflictException_whenDuplicatedIdIsProvided() {
             // given
-            String userId = "ajchoi0928";
-            String userName = "junho";
-            String description = "loopers backend developer";
-            String email = "loopers@loopers.com";
-            String birthDate = "1997-09-28";
-            String gender = "M";
-
-            UserCommand.Create create = new UserCommand.Create(userId, userName
-                    , description, email, birthDate, gender, 0);
-
-            UserModel someUser = userService.createUser(create);
+            UserCommand.Create newUserCreate = createCommand;
+            UserModel someUser = userService.createUser(newUserCreate);
 
             // when
             CoreException result = assertThrows(CoreException.class, () -> {
-                userService.createUser(create);
+                userService.createUser(newUserCreate);
             });
 
             // then
@@ -119,18 +119,8 @@ void throwsConflictException_whenDuplicatedIdIsProvided() {
         @Test
         void getUserById_whenExistUserIdIsProvided() {
             // given
-            String userId = "ajchoi0928";
-            String userName = "junho";
-            String description = "loopers backend developer";
-            String email = "loopers@loopers.com";
-            String birthDate = "1997-09-28";
-            String gender = "M";
-            int defaultPoint = 0;
-
-            UserCommand.Create create = new UserCommand.Create(userId, userName
-                    , description, email, birthDate, gender, 0);
-
-            UserModel someUser = userService.createUser(create);
+            UserCommand.Create newUserCreate = createCommand;
+            UserModel someUser = userService.createUser(newUserCreate);
 
             // when
             UserModel foundUser = userService.getUserOrNull(userId);
@@ -144,7 +134,7 @@ void getUserById_whenExistUserIdIsProvided() {
                     () -> assertThat(foundUser.getEmail()).isEqualTo(email),
                     () -> assertThat(foundUser.getBirthDate()).isEqualTo(birthDate),
                     () -> assertThat(foundUser.getGender()).isEqualTo(gender),
-                    () -> assertThat(foundUser.getPoint()).isEqualTo(defaultPoint)
+                    () -> assertThat(foundUser.getPoint()).isEqualTo(newUserPointPolicy)
             );
 
         }
@@ -154,8 +144,6 @@ void getUserById_whenExistUserIdIsProvided() {
         void getUserById_whenNotExistUserIdIsProvided() {
             // given
             String userId = "ajchoi0928";
-
-            // when(userRepositoryMock.findByUserId(userId)).thenReturn(Optional.empty());
             if(userRepository.existsUserId(userId)) {
                 userRepository.deleteUser(userId);
             }

From 10ec26213417e2b990d755fcb7487c5b6a3f98d4 Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Sun, 2 Nov 2025 16:35:54 +0900
Subject: [PATCH 39/40] =?UTF-8?q?fix:=20import=20=ED=8C=A8=ED=82=A4?=
 =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../src/main/java/com/loopers/domain/user/UserService.java       | 1 -
 1 file changed, 1 deletion(-)

diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
index 819616cd3..8fef0511a 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/user/UserService.java
@@ -1,7 +1,6 @@
 package com.loopers.domain.user;
 
 import com.loopers.application.user.UserCommand;
-import com.loopers.application.user.UserInfo;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;

From e8bb28b851bf4346a2e83485ebe717cd808eda3b Mon Sep 17 00:00:00 2001
From: junoade <ajchoi0928@gmail.com>
Date: Mon, 3 Nov 2025 22:45:49 +0900
Subject: [PATCH 40/40] =?UTF-8?q?fix:=20import=20=ED=8C=A8=ED=82=A4?=
 =?UTF-8?q?=EC=A7=80=20=EC=A0=95=EB=A6=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

1) setup() 메소드 호출시 생성되는 userModel 객체
2) mocking된 userRepository, 회원정보 있다고 mocking
3) 포인트 충전 비즈니스 로직 수행 후 save() 된 userModel을 읽고 싶다면

4) ArguementCaptor 를 비즈니스 로직 전에 설정하고, 테스트할 서비스 호출 중 userModel을 캡쳐함
5) 캡쳐본과 검증
---
 .../domain/user/UserServicePointTest.java        | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
index 265d8354d..26add3a2b 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServicePointTest.java
@@ -6,6 +6,7 @@
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -96,20 +97,23 @@ void throwBadRequestException_whenChargePointUserDoesNotExist() {
     void doChargePoint_whenUserExists() {
         // given
         String userId = userModel.getUserId();
-        int point = 500;
+        int originalPoint = userModel.getPoint();
+        int newPoint = 500;
         given(userRepository.findByUserId(userId))
                 .willReturn(Optional.of(userModel));
 
         // when
-        Integer afterPoint = userService.chargePoint(userId, point);
+        ArgumentCaptor<UserModel> userCaptor = ArgumentCaptor.forClass(UserModel.class);
+        Integer afterPoint = userService.chargePoint(userId, newPoint);
 
         // then
-        UserModel afterUser = userRepository.findByUserId(userId).get();
-        assertThat(afterPoint).isEqualTo(userModel.getPoint()); // 상태
-        assertThat(afterPoint).isEqualTo(afterUser.getPoint());
+        assertThat(afterPoint).isEqualTo(originalPoint + newPoint); // 상태
 
         verify(userRepository, atLeastOnce()).findByUserId(userId); // 행위
-        verify(userRepository, times(1)).save(userModel); //  행위
+        verify(userRepository, times(1)).save(userCaptor.capture()); // save에 전달된 객체 캡처
+
+        // 캡처된 객체가 올바르게 업데이트되었는지 검증
+        assertThat(userCaptor.getValue().getPoint()).isEqualTo(originalPoint + newPoint);
     }
 
 
