From ed5565313b9871aee227380de5bee88af7ecdf3e Mon Sep 17 00:00:00 2001 From: leejunnyeop Date: Sun, 18 May 2025 05:48:16 +0900 Subject: [PATCH 1/4] =?UTF-8?q?test:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=8B=A8=EC=9C=84=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 - getUserTotalPoint(): 포인트 조회 성공 및 존재하지 않을 경우 0 반환 테스트 - usingPoint(): 포인트 사용 성공, 부족 예외, 포인트 정보 미존재 예외 테스트 --- .../point/service/PointServiceV1Test.java | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java diff --git a/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java b/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java new file mode 100644 index 0000000..6c83b97 --- /dev/null +++ b/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java @@ -0,0 +1,126 @@ +package com.example.hackathon.domain.point.service; + +import com.example.hackathon.domain.point.dto.PointRequestDTO; +import com.example.hackathon.domain.point.entity.Point; +import com.example.hackathon.domain.point.entity.ProductType; +import com.example.hackathon.domain.point.repository.PointRepository; +import com.example.hackathon.domain.user.entity.User; +import com.example.hackathon.domain.user.repository.UserRepository; +import com.example.hackathon.global.exception.BusinessException; +import com.example.hackathon.global.response.Code; + +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 java.util.Optional; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PointServiceV1Test { + + @Mock + private PointRepository pointRepository; + + @Mock + private UserRepository userRepository; + + @InjectMocks + private PointService pointService; + + @Mock + private User user; + + private Point mockPoint; + + Long userId = 1L; + + @BeforeEach + void setUp() { + mockPoint = new Point(); + mockPoint.setUser(user); + mockPoint.setTotalPoint(500); + } + + @Test + @DisplayName("사용자 포인트 조회 - 성공 (500 포인트)") + void getUserTotalPoint_success() { + // given + when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + + // when + int totalPoint = pointService.getUserTotalPoint(userId); + + // then + assertThat(totalPoint).isEqualTo(500); + } + + @Test + @DisplayName("사용자 포인트 조회 - 정보 없을 경우 0 반환") + void getUserTotalPoint_zeroWhenNotExists() { + // given + when(pointRepository.findByUserId(userId)).thenReturn(Optional.empty()); + + // when + int totalPoint = pointService.getUserTotalPoint(userId); + + // then + assertThat(totalPoint).isEqualTo(0); + } + + @Test + @DisplayName("포인트 사용 - 성공") + void usingPoint_success() { + // given + PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() + .productType(ProductType.KEYRING) // 예: KEYRING은 200포인트 필요 + .build(); + + when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + + // when + pointService.usingPoint(userId, req); + + // then + assertThat(mockPoint.getTotalPoint()).isEqualTo(279); // 500 - 200 + } + + @Test + @DisplayName("포인트 사용 - 잔여 포인트 부족 시 예외 발생") + void usingPoint_notEnoughPoint_throwsException() { + // given + PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() + .productType(ProductType.KEYRING) // 예: 키링 + .build(); + + when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + + // expect + assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(Code.POINT_NOT_ENOUGH.getMessage()); + } + + @Test + @DisplayName("포인트 사용 - 사용자 포인트 정보가 없을 시 예외 발생") + void usingPoint_pointEntityNotFound_throwsException() { + // given + PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() + .productType(ProductType.KEYRING) + .build(); + + when(pointRepository.findByUserId(userId)).thenReturn(Optional.empty()); + + // expect + assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + .isInstanceOf(BusinessException.class) + .hasMessageContaining(Code.POINT_NOT_FOUND.getMessage()); + } +} From 9c2a93ded52ddb722b1b4ea60c21bc9fffc4c32c Mon Sep 17 00:00:00 2001 From: leejunnyeop Date: Sun, 18 May 2025 05:49:08 +0900 Subject: [PATCH 2/4] =?UTF-8?q?test:=20ActivityService=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=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 - 카테고리별 활동 및 오늘의 활동 조회 성공 테스트 - 단일 활동 조회: 존재할 경우 반환, 없을 경우 null 반환 테스트 - 사용자 활동 추가: 성공 케이스 및 사용자/카테고리 없음 예외 테스트 --- .../activity/service/ActivityServiceTest.java | 149 ++++++++++++------ 1 file changed, 97 insertions(+), 52 deletions(-) diff --git a/src/test/java/com/example/hackathon/domain/activity/service/ActivityServiceTest.java b/src/test/java/com/example/hackathon/domain/activity/service/ActivityServiceTest.java index 5b756b0..33e9293 100644 --- a/src/test/java/com/example/hackathon/domain/activity/service/ActivityServiceTest.java +++ b/src/test/java/com/example/hackathon/domain/activity/service/ActivityServiceTest.java @@ -1,10 +1,13 @@ package com.example.hackathon.domain.activity.service; -import com.example.hackathon.domain.point.dto.PointRequestDTO; -import com.example.hackathon.domain.point.entity.Point; -import com.example.hackathon.domain.point.entity.ProductType; -import com.example.hackathon.domain.point.repository.PointRepository; -import com.example.hackathon.domain.point.service.PointService; +import com.example.hackathon.domain.activity.dto.ActivityNewRequestDto; +import com.example.hackathon.domain.activity.dto.ActivityResponseDto; +import com.example.hackathon.domain.activity.entity.Activity; +import com.example.hackathon.domain.activity.entity.RepeatCycle; +import com.example.hackathon.domain.activity.repository.ActivityRepository; +import com.example.hackathon.domain.category.entity.Category; +import com.example.hackathon.domain.category.entity.CategoryType; +import com.example.hackathon.domain.category.repository.CategoryRepository; import com.example.hackathon.domain.user.entity.User; import com.example.hackathon.domain.user.repository.UserRepository; import com.example.hackathon.global.exception.BusinessException; @@ -15,6 +18,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import static org.assertj.core.api.Assertions.*; @@ -25,103 +30,143 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class PointServiceTest { +class ActivityServiceTest { - @Mock - private PointRepository pointRepository; - - @Mock - private UserRepository userRepository; + @Mock private ActivityRepository activityRepository; + @Mock private UserRepository userRepository; + @Mock private CategoryRepository categoryRepository; @InjectMocks - private PointService pointService; + private ActivityService activityService; + + private Activity mockActivity; + + @BeforeEach + void setup() { + mockActivity = Activity.builder() + .id(1L) + .description("Test activity") + .point(100) + .sortOrder(1) + .isCustom(false) + .build(); + } - @Mock - private User user; + @Test + @DisplayName("카테고리별 활동 조회 성공") + void getActivitiesByCategoryWithTodayFlags_success() { + // given + when(activityRepository.findByCategoryIdAndIsDisplayedTrueOrderBySortOrderAsc(1L)) + .thenReturn(List.of(mockActivity)); - private Point mockPoint; + List todayIds = List.of(1L); - Long userId = 1L; + // when + List result = activityService.getActivitiesByCategoryWithTodayFlags(1L, todayIds); - @BeforeEach - void setUp() { - mockPoint = new Point(); - mockPoint.setUser(user); - mockPoint.setTotalPoint(500); + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).isTodayActivity()).isTrue(); + } + + @Test + @DisplayName("오늘의 미션 활동 조회 성공") + void getTodayMissionActivitiesWithTodayFlags_success() { + // given + when(activityRepository.findByIsDisplayedTrueAndIsTodayActivityTrueOrderBySortOrderAsc()) + .thenReturn(List.of(mockActivity)); + + List todayIds = List.of(1L); + + // when + List result = activityService.getTodayMissionActivitiesWithTodayFlags(todayIds); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).isTodayActivity()).isTrue(); } @Test - @DisplayName("사용자 포인트 조회 - 성공 (500 포인트)") - void getUserTotalPoint_success() { + @DisplayName("단일 활동 조회 - 존재할 경우") + void getActivity_exists() { // given - when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + when(activityRepository.findById(1L)).thenReturn(Optional.of(mockActivity)); // when - int totalPoint = pointService.getUserTotalPoint(userId); + ActivityResponseDto result = activityService.getActivity(1L); // then - assertThat(totalPoint).isEqualTo(500); + assertThat(result).isNotNull(); + assertThat(result.getId()).isEqualTo(1L); } @Test - @DisplayName("사용자 포인트 조회 - 정보 없을 경우 0 반환") - void getUserTotalPoint_zeroWhenNotExists() { + @DisplayName("단일 활동 조회 - 존재하지 않을 경우 null 반환") + void getActivity_notExists() { // given - when(pointRepository.findByUserId(userId)).thenReturn(Optional.empty()); + when(activityRepository.findById(1L)).thenReturn(Optional.empty()); // when - int totalPoint = pointService.getUserTotalPoint(userId); + ActivityResponseDto result = activityService.getActivity(1L); // then - assertThat(totalPoint).isEqualTo(0); + assertThat(result).isNull(); } @Test - @DisplayName("포인트 사용 - 성공") - void usingPoint_success() { + @DisplayName("사용자 활동 추가 성공") + void addUserActivities_success() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) // 예: KEYRING은 200포인트 필요 + User user = mock(User.class); + Category category = mock(Category.class); + ActivityNewRequestDto.AddActivity req = ActivityNewRequestDto.AddActivity.builder() + .categoryType(CategoryType.CONSUMPTION) + .description("설명") + .title("제목") + .repeatCycle(RepeatCycle.DAILY) .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + when(userRepository.findByKakaoId(1L)).thenReturn(Optional.of(user)); + when(categoryRepository.findByCategoryType(CategoryType.CONSUMPTION)).thenReturn(Optional.of(category)); // when - pointService.usingPoint(userId, req); + activityService.addUserActivities(1L, req); // then - assertThat(mockPoint.getTotalPoint()).isEqualTo(279); // 500 - 200 + verify(activityRepository, times(1)).save(any(Activity.class)); } @Test - @DisplayName("포인트 사용 - 잔여 포인트 부족 시 예외 발생") - void usingPoint_notEnoughPoint_throwsException() { + @DisplayName("사용자 활동 추가 - 사용자 없음 예외") + void addUserActivities_userNotFound() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) // 예: 키링 + ActivityNewRequestDto.AddActivity req = ActivityNewRequestDto.AddActivity.builder() + .categoryType(CategoryType.CONSUMPTION) .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); + when(userRepository.findByKakaoId(1L)).thenReturn(Optional.empty()); // expect - assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + assertThatThrownBy(() -> activityService.addUserActivities(1L, req)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(Code.POINT_NOT_ENOUGH.getMessage()); + .hasMessageContaining(Code.USER_NOT_FOUND.getMessage()); } @Test - @DisplayName("포인트 사용 - 사용자 포인트 정보가 없을 시 예외 발생") - void usingPoint_pointEntityNotFound_throwsException() { + @DisplayName("사용자 활동 추가 - 카테고리 없음 예외") + void addUserActivities_categoryNotFound() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) + User user = mock(User.class); + ActivityNewRequestDto.AddActivity req = ActivityNewRequestDto.AddActivity.builder() + .categoryType(CategoryType.CONSUMPTION) .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.empty()); + when(userRepository.findByKakaoId(1L)).thenReturn(Optional.of(user)); + when(categoryRepository.findByCategoryType(CategoryType.CONSUMPTION)).thenReturn(Optional.empty()); // expect - assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + assertThatThrownBy(() -> activityService.addUserActivities(1L, req)) .isInstanceOf(BusinessException.class) - .hasMessageContaining(Code.POINT_NOT_FOUND.getMessage()); + .hasMessageContaining(Code.CATEGORY_NOT_FOUND.getMessage()); } } From c18c1a3ca6f20f3d57a711ca91b670568b2f10a6 Mon Sep 17 00:00:00 2001 From: "JUNHWAN(Chris) JANG" <62946867+itsChrisJang@users.noreply.github.com> Date: Sun, 18 May 2025 05:52:45 +0900 Subject: [PATCH 3/4] =?UTF-8?q?docs:=20README=20=EC=B5=9C=EC=8B=A0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 303a3b5..25b70fc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ -# Be +# 🌿 Leafy - 기후를 지키는 오늘의 실천 +**Leafy(리피)** 는 일상 속 작은 실천을 통해 +기후 변화에 대응할 수 있도록 동기를 부여하는 실천 기반 플랫폼입니다. +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0001](https://github.com/user-attachments/assets/c4203777-15ac-4aee-9199-835d336cc198) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0004](https://github.com/user-attachments/assets/8e6398cd-86a5-4abc-9fda-5bcf405e2f6d) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0003](https://github.com/user-attachments/assets/2657f0ff-2929-4b7c-a79a-6b8967af47c5) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0005](https://github.com/user-attachments/assets/3b32b9d2-e605-4f39-88b9-0d7b71b77b25) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0007](https://github.com/user-attachments/assets/6f813687-357d-46f9-b060-47506279dbea) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0017](https://github.com/user-attachments/assets/db4488ad-b1e1-417e-b93f-e7fa03bc0ea8) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0008](https://github.com/user-attachments/assets/5f8d118a-5320-4219-ab30-4d4f532ddc01) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0009](https://github.com/user-attachments/assets/8882bff1-4cb5-435c-a3fb-39ec2858ed53) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0010](https://github.com/user-attachments/assets/ee6a2833-be60-470a-ba33-869e15cd1eda) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0011](https://github.com/user-attachments/assets/71451d9e-aaef-4bc4-ba9d-8c3649e2a6d4) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0012](https://github.com/user-attachments/assets/eb221d99-f66a-42cb-bc2c-e5a3518c718c) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0014](https://github.com/user-attachments/assets/facd94c8-33ac-4b0b-9d89-d22a9fcb3926) +![8th Ne(o)rdinary Hackathon_A조_Leafy (1)_page-0016](https://github.com/user-attachments/assets/6fe23d15-d4e0-48e5-9473-05d69e6bf236) -### 아키텍처 -image From c13f9b35f165288a680f5ce09c05a17e9d68a346 Mon Sep 17 00:00:00 2001 From: Chris Jang Date: Sun, 18 May 2025 06:00:56 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facade/ProductPurchaseFacade.java | 2 +- .../point/service/PointServiceV1Test.java | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/example/hackathon/domain/productpurchasehistory/facade/ProductPurchaseFacade.java b/src/main/java/com/example/hackathon/domain/productpurchasehistory/facade/ProductPurchaseFacade.java index 37a5d9a..393b7f9 100644 --- a/src/main/java/com/example/hackathon/domain/productpurchasehistory/facade/ProductPurchaseFacade.java +++ b/src/main/java/com/example/hackathon/domain/productpurchasehistory/facade/ProductPurchaseFacade.java @@ -56,6 +56,6 @@ public void purchase(Long userId, ProductPurchaseRequestDto request) { productPurchaseHistoryService.purchase(history); -// pointService.usingPoint(); + pointService.updatePointBalance(userId, product.price()); } } diff --git a/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java b/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java index 6c83b97..37fb063 100644 --- a/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java +++ b/src/test/java/com/example/hackathon/domain/point/service/PointServiceV1Test.java @@ -79,14 +79,10 @@ void getUserTotalPoint_zeroWhenNotExists() { @DisplayName("포인트 사용 - 성공") void usingPoint_success() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) // 예: KEYRING은 200포인트 필요 - .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); // when - pointService.usingPoint(userId, req); + pointService.updatePointBalance(userId, ProductType.KEYRING.getPoint()); // then assertThat(mockPoint.getTotalPoint()).isEqualTo(279); // 500 - 200 @@ -96,14 +92,10 @@ void usingPoint_success() { @DisplayName("포인트 사용 - 잔여 포인트 부족 시 예외 발생") void usingPoint_notEnoughPoint_throwsException() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) // 예: 키링 - .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.of(mockPoint)); // expect - assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + assertThatThrownBy(() -> pointService.updatePointBalance(userId, ProductType.KEYRING.getPoint())) .isInstanceOf(BusinessException.class) .hasMessageContaining(Code.POINT_NOT_ENOUGH.getMessage()); } @@ -112,14 +104,10 @@ void usingPoint_notEnoughPoint_throwsException() { @DisplayName("포인트 사용 - 사용자 포인트 정보가 없을 시 예외 발생") void usingPoint_pointEntityNotFound_throwsException() { // given - PointRequestDTO.buyProductReq req = PointRequestDTO.buyProductReq.builder() - .productType(ProductType.KEYRING) - .build(); - when(pointRepository.findByUserId(userId)).thenReturn(Optional.empty()); // expect - assertThatThrownBy(() -> pointService.usingPoint(userId, req)) + assertThatThrownBy(() -> pointService.updatePointBalance(userId, ProductType.KEYRING.getPoint())) .isInstanceOf(BusinessException.class) .hasMessageContaining(Code.POINT_NOT_FOUND.getMessage()); }