diff --git a/src/main/java/umc7th/bulk/BulkApplication.java b/src/main/java/umc7th/bulk/BulkApplication.java index d3ea613..ea2dc09 100644 --- a/src/main/java/umc7th/bulk/BulkApplication.java +++ b/src/main/java/umc7th/bulk/BulkApplication.java @@ -4,9 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableJpaAuditing +@EnableScheduling public class BulkApplication { public static void main(String[] args) { diff --git a/src/main/java/umc7th/bulk/record/repository/RecordRepository.java b/src/main/java/umc7th/bulk/record/repository/RecordRepository.java index a3ffbea..f52fa76 100644 --- a/src/main/java/umc7th/bulk/record/repository/RecordRepository.java +++ b/src/main/java/umc7th/bulk/record/repository/RecordRepository.java @@ -22,4 +22,5 @@ public interface RecordRepository extends JpaRepository { @Query("SELECT r FROM Record r WHERE r.user = :user AND r.date = :date") List findByUserAndDate(@Param("user") User user, @Param("date") LocalDate date); + boolean existsByUserAndDate(User user, LocalDate date); } \ No newline at end of file diff --git a/src/main/java/umc7th/bulk/record/service/RecordCompleteResetService.java b/src/main/java/umc7th/bulk/record/service/RecordCompleteResetService.java new file mode 100644 index 0000000..7b763ef --- /dev/null +++ b/src/main/java/umc7th/bulk/record/service/RecordCompleteResetService.java @@ -0,0 +1,28 @@ +package umc7th.bulk.record.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc7th.bulk.user.repository.UserRepository; + +@Slf4j +@Service +@RequiredArgsConstructor +public class RecordCompleteResetService { + + private final UserRepository userRepository; + + /** + * 매일 자정에 record_complete 값을 false로 초기화하는 배치 작업 + */ + + @Transactional + @Scheduled(cron = "0 0 0 * * ?") + public void resetRecordComplete() { + log.info("매일 자정 record_complete 값 초기화 시작..."); + int updatedCount = userRepository.resetRecordComplete(); + log.info("✅ 초기화 완료: {}명의 record_complete 값을 false로 변경", updatedCount); + } +} diff --git a/src/main/java/umc7th/bulk/record/service/RecordServiceImpl.java b/src/main/java/umc7th/bulk/record/service/RecordServiceImpl.java index 23b1c19..f11e1a4 100644 --- a/src/main/java/umc7th/bulk/record/service/RecordServiceImpl.java +++ b/src/main/java/umc7th/bulk/record/service/RecordServiceImpl.java @@ -7,6 +7,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import umc7th.bulk.global.error.GeneralErrorCode; +import umc7th.bulk.global.error.exception.CustomException; +import umc7th.bulk.group.entity.Group; +import umc7th.bulk.group.repository.GroupRepository; import umc7th.bulk.meal.entity.MealType; import umc7th.bulk.mealItem.entity.MealItem; import umc7th.bulk.mealMealItemMapping.entity.MealMealItemMapping; @@ -19,6 +23,8 @@ import umc7th.bulk.record.upload.S3Service; import umc7th.bulk.recordedFood.entity.RecordedFood; import umc7th.bulk.recordedFood.repository.RecordedFoodRepository; +import umc7th.bulk.stageRecord.entity.StageRecord; +import umc7th.bulk.stageRecord.repository.StageRecordRepository; import umc7th.bulk.user.domain.User; import umc7th.bulk.user.repository.UserRepository; import umc7th.bulk.user.service.UserService; @@ -42,6 +48,9 @@ public class RecordServiceImpl implements RecordService { private final UserService userService; private final UserRepository userRepository; + private final StageRecordRepository stageRecordRepository; + private final GroupRepository groupRepository; + @Transactional public RecordResponseDto createRecord(RecordRequestDto.Create requestDto) { // 사용자 조회 @@ -58,6 +67,10 @@ public RecordResponseDto createRecord(RecordRequestDto.Create requestDto) { throw new IllegalArgumentException("이미 해당 날짜와 끼니에 대한 기록이 존재합니다."); } + // 사용자의 해당 날짜 기록 확인 + boolean hasRecordToday = recordRepository.existsByUserAndDate(user, requestDto.getDate()); + + // 사용자의 끼니(MealType)에 해당하는 식단 데이터 조회 List mealMappings = mealMealItemMappingRepository.findByMeal_LocalDateAndMeal_Type( requestDto.getDate(), requestDto.getMealType()); @@ -80,6 +93,14 @@ public RecordResponseDto createRecord(RecordRequestDto.Create requestDto) { Record savedRecord = recordRepository.save(record); + // 하루에 한 번이라도 기록하면 record_complete = true 설정 + if (!hasRecordToday) { + user.markRecordComplete(); + userRepository.save(user); + } + + checkAndAdvanceStage(user.getGroup()); + // MealItem을 기반으로 RecordedFood 생성 List recordedFoods = mealMappings.stream() .map(MealMealItemMapping::getMealItem) @@ -158,6 +179,10 @@ public RecordResponseDto createNotFollowedRecord(RecordRequestDto.CreateNotFollo // MealType 변환 MealType type = requestDto.getMealType(); + // 사용자의 해당 날짜 기록 확인 + boolean hasRecordToday = recordRepository.existsByUserAndDate(user, requestDto.getDate()); + + String uploadedImageUrl = null; String gptRawResponseString = null; @@ -262,6 +287,14 @@ public RecordResponseDto createNotFollowedRecord(RecordRequestDto.CreateNotFollo Record savedRecord = recordRepository.save(record); log.info("✅ Record 저장 완료: recordId={}", savedRecord.getId()); + // 하루에 한 번이라도 기록하면 record_complete = true 설정 + if (!hasRecordToday) { + user.markRecordComplete(); + userRepository.save(user); + } + + checkAndAdvanceStage(user.getGroup()); + // Response 생성 return RecordResponseDto.builder() .recordId(savedRecord.getId()) @@ -356,5 +389,41 @@ public RecordResponseDto.TodaySummary getTodayRecord(User user) { .build(); } + public void checkAndAdvanceStage(Group group) { + // 현재 그룹에서 recordComplete = true인 유저 수 확인 + int recordedCount = userRepository.countByGroupAndRecordCompleteTrue(group); + + if (recordedCount >= 5) { + advanceStage(group); + } + } + + private void advanceStage(Group group) { + // 현재 그룹의 가장 최신 스테이지 가져오기 + StageRecord currentStageRecord = stageRecordRepository + .findTopByGroupOrderByStageNumberDesc(group) + .orElseThrow(() -> new CustomException(GeneralErrorCode.GROUP_NOT_FOUND_404)); + + // 현재 스테이지 완료 처리 + currentStageRecord.completeStage(); + stageRecordRepository.save(currentStageRecord); + + // 그룹의 현재 스테이지 증가 + group.advanceStage(); + groupRepository.save(group); + + // 새로운 스테이지 기록 생성 + StageRecord newStageRecord = StageRecord.builder() + .group(group) + .stageNumber(group.getCurrentStage()) + .totalUsers((int) userRepository.countByGroup(group)) + .recordedUsers(0) // 새로운 스테이지이므로 기록된 사용자 0명부터 시작 + .isCompleted(false) + .build(); + + stageRecordRepository.save(newStageRecord); + } + + } \ No newline at end of file diff --git a/src/main/java/umc7th/bulk/stageRecord/entity/StageRecord.java b/src/main/java/umc7th/bulk/stageRecord/entity/StageRecord.java index 3b6f578..c4bd3c5 100644 --- a/src/main/java/umc7th/bulk/stageRecord/entity/StageRecord.java +++ b/src/main/java/umc7th/bulk/stageRecord/entity/StageRecord.java @@ -50,6 +50,8 @@ public void increaseRecordedUsers() { this.recordedUsers++; } + public void increaseTotalUsers() { this.totalUsers++; } + // 스테이지 완료 처리 로직 public void completeStage() { this.isCompleted = true; diff --git a/src/main/java/umc7th/bulk/stageRecord/repository/StageRecordRepository.java b/src/main/java/umc7th/bulk/stageRecord/repository/StageRecordRepository.java index c8f4b3f..b3a5ff2 100644 --- a/src/main/java/umc7th/bulk/stageRecord/repository/StageRecordRepository.java +++ b/src/main/java/umc7th/bulk/stageRecord/repository/StageRecordRepository.java @@ -1,10 +1,14 @@ package umc7th.bulk.stageRecord.repository; import org.springframework.data.jpa.repository.JpaRepository; +import umc7th.bulk.group.entity.Group; import umc7th.bulk.stageRecord.entity.StageRecord; import java.util.List; +import java.util.Optional; public interface StageRecordRepository extends JpaRepository { List findByGroupGroupIdOrderByStageNumberAsc(Long groupId); + + Optional findTopByGroupOrderByStageNumberDesc(Group group); } diff --git a/src/main/java/umc7th/bulk/user/domain/User.java b/src/main/java/umc7th/bulk/user/domain/User.java index b91b05a..811abd1 100644 --- a/src/main/java/umc7th/bulk/user/domain/User.java +++ b/src/main/java/umc7th/bulk/user/domain/User.java @@ -76,7 +76,7 @@ public class User extends BaseTimeEntity { private String favoriteFood; @Column(nullable = false, name = "record_complete") - private boolean recordComplete; + private boolean recordComplete = false; @Column(nullable = false, name = "access_token") private String accessToken; @@ -168,6 +168,13 @@ public void setGroup(Group group) { } } + + public void markRecordComplete() { + if (!this.recordComplete) { + this.recordComplete = true; + } + + } public void updateCurrentNutrients(Long calories, Long carbos, Long proteins, Long fats) { this.curCalories += calories; this.curCarbos += carbos; diff --git a/src/main/java/umc7th/bulk/user/repository/UserRepository.java b/src/main/java/umc7th/bulk/user/repository/UserRepository.java index 7d6e8a5..b6de523 100644 --- a/src/main/java/umc7th/bulk/user/repository/UserRepository.java +++ b/src/main/java/umc7th/bulk/user/repository/UserRepository.java @@ -1,6 +1,11 @@ package umc7th.bulk.user.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import umc7th.bulk.group.entity.Group; import umc7th.bulk.user.domain.User; import java.util.List; @@ -14,4 +19,22 @@ public interface UserRepository extends JpaRepository { List findByGroupGroupIdAndRecordCompleteTrue(Long groupId); // 해당 그룹 id 에서 오늘 기록 달성한 사용자 리스트 Optional findByKakaoId(String kakaoId); Optional findByEmail(String email); + + @Modifying + @Transactional + @Query("UPDATE User u SET u.recordComplete = false") + int resetRecordComplete(); + + // 특정 그룹 기록 달성한 인원 수 조회 (오늘 기록 완료한 인원 수) + @Query("SELECT COUNT(u) FROM User u WHERE u.group = :group AND u.recordComplete = true") + int countByGroupAndRecordCompleteTrue(@Param("group") Group group); + + // 특정 그룹 총 인원 수 조회 + @Query("SELECT COUNT(u) FROM User u WHERE u.group = :group") + int countByGroup(@Param("group") Group group); + + + + + } diff --git a/src/main/java/umc7th/bulk/user/service/command/UserCommandServiceImpl.java b/src/main/java/umc7th/bulk/user/service/command/UserCommandServiceImpl.java index dc8385f..cf0c605 100644 --- a/src/main/java/umc7th/bulk/user/service/command/UserCommandServiceImpl.java +++ b/src/main/java/umc7th/bulk/user/service/command/UserCommandServiceImpl.java @@ -9,6 +9,8 @@ import umc7th.bulk.global.jwt.util.JwtProvider; import umc7th.bulk.group.entity.Group; import umc7th.bulk.group.repository.GroupRepository; +import umc7th.bulk.stageRecord.entity.StageRecord; +import umc7th.bulk.stageRecord.repository.StageRecordRepository; import umc7th.bulk.user.domain.User; import umc7th.bulk.user.dto.UserRequestDTO; import umc7th.bulk.user.dto.UserResponseDTO; @@ -30,6 +32,7 @@ public class UserCommandServiceImpl implements UserCommandService { private final JwtProvider jwtProvider; private final BulkCharacterRepository bulkCharacterRepository; private final GroupRepository groupRepository; + private final StageRecordRepository stageRecordRepository; @Override public UserResponseDTO.UserTokenDTO signup(UserRequestDTO.SignupDTO dto) { @@ -69,9 +72,26 @@ public UserResponseDTO.UserTokenDTO signup(UserRequestDTO.SignupDTO dto) { .currentStage(1) .endDate(LocalDateTime.now().plusDays(7)) // 그룹 종료일 7일 후 설정 .build(); - return groupRepository.save(newGroup); + groupRepository.save(newGroup); + + StageRecord firstStage = StageRecord.builder() + .group(newGroup) + .stageNumber(1) + .totalUsers(1) + .recordedUsers(0) + .isCompleted(false) + .build(); + stageRecordRepository.save(firstStage); + + return newGroup; }); + if (!groupRepository.findGroupWithSpace().isPresent()) { // 기존 그룹인지 확인 + StageRecord latestStage = stageRecordRepository.findTopByGroupOrderByStageNumberDesc(group) + .orElseThrow(() -> new RuntimeException("StageRecord not found for existing group.")); + latestStage.increaseTotalUsers(); + stageRecordRepository.save(latestStage); + } // User 저장 (BulkCharacter 포함) diff --git a/src/main/resources/certificate.crt b/src/main/resources/certificate.crt new file mode 100644 index 0000000..e26eefa --- /dev/null +++ b/src/main/resources/certificate.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUWrOnf2nFmTI+YEg8AiaSqry4HPswDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTAyMTkxMjI2MjJaFw0yNjAy +MTkxMjI2MjJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCowVmGzO1HRrd22gUE3U66jSaBN1I9Zw8Uo7o9fUch +gAvDZUEY7zWlhCwWvyXaaG6opVwBH/n8Hwm+x+BSAqhcBfZyP6Cn5wNeXEn69Hx/ +4uIR3lrKo8cFeSm6ukwDuEi/Rk8Yp4Sfruu1ErDLJx/daA5HS6ikA7oyZpfMJN7x +NOs3bmbyrPvgCqEty+sGa3/tXPv8ocRjt829stDBVsEjEa6F7VcRRchIqIswA63B +KxJUTKI59NsLznr1hRsIOInwjEXlUPF2KwMwva+Eb0cA3SoXh9fizLMOtJ6lW5as +Xucm+spSV8EpfPkWi+Bn3z7DRfydtKzr0oWVMMObjLtDAgMBAAGjUzBRMB0GA1Ud +DgQWBBQNFGQPkXx1xq+hg8NvMEFqJYwpKjAfBgNVHSMEGDAWgBQNFGQPkXx1xq+h +g8NvMEFqJYwpKjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCd +ntLw/fXZpmH8RZ5W/972iBE0w4ZzxsdoXAKcMQUOoQDsypQLf9ZuUYYf3zuvUEjT +Rc8PWSnB44UHBi0AgHFmjOED+ct2bLOopWEJQ7/hXcwg9a1hQu4XG78RSG6FmX7q +frOUGZLAcVV3Yr7hR8BwVny4S/Fuze2u/SnnbgH1Y0KDvDlhhOfYjNDdDmRb+thI +zfpvSDE9ojvC/PwT/1pXPoLBb/BkILOKEeASmHUgb4LI1D3+Ak2z0tpPUAKmI+/P +b9NVAtuCkR6UvvGhxYklAnAdJIfn5NzRS+DVzHq3CgrCGVKfaShjZJT7HNu3Qqe9 +ZZWG85U/eCbbl0FVRB5o +-----END CERTIFICATE----- diff --git a/src/main/resources/keystore.p12 b/src/main/resources/keystore.p12 new file mode 100644 index 0000000..57bff3e Binary files /dev/null and b/src/main/resources/keystore.p12 differ diff --git a/src/main/resources/private.key b/src/main/resources/private.key new file mode 100644 index 0000000..7c9c928 --- /dev/null +++ b/src/main/resources/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCowVmGzO1HRrd2 +2gUE3U66jSaBN1I9Zw8Uo7o9fUchgAvDZUEY7zWlhCwWvyXaaG6opVwBH/n8Hwm+ +x+BSAqhcBfZyP6Cn5wNeXEn69Hx/4uIR3lrKo8cFeSm6ukwDuEi/Rk8Yp4Sfruu1 +ErDLJx/daA5HS6ikA7oyZpfMJN7xNOs3bmbyrPvgCqEty+sGa3/tXPv8ocRjt829 +stDBVsEjEa6F7VcRRchIqIswA63BKxJUTKI59NsLznr1hRsIOInwjEXlUPF2KwMw +va+Eb0cA3SoXh9fizLMOtJ6lW5asXucm+spSV8EpfPkWi+Bn3z7DRfydtKzr0oWV +MMObjLtDAgMBAAECggEAS4z4hEmlpyXpSCv2WEWuFAXSacI3Luc/UKm2XQYDvOND +IPDpcnzRoy7nwC1GiJ/9fsI9TQGgXPgWWFPKwVyQPFatDVwgFFtv3iRqOJCRVn3g +YUiPlC47kV87x+3Uz0uHQh77lVmWHhllTFU4UlNLSEfoMJIWR4ulph/ZMNuG9ixU +IBftnOUp0RlPpxl1nsRmNs70NkOiF9bMHjZ8KNd6RwKLFNLBeunBQdTGMz5/wqew +no5F/KR0eMdVWaM6movM//Q0KmIm6jdu8e2ZYMjOOsuDFE+E2VcXCPWUtuUKWdEF +f/h2oPcfvW29/KjlCWHJilJirKvUh4dh5GIgWuA8CQKBgQDotHdEbmZyE2MQzRXh +Plh1xQ13iJ3gx+j0x7tt1NluZ+lE6BtN0xFwvYC4rH3cvoh4TWatAAan3nnb0o3a +Rw7yromYyQFIRzU1PsiSt1Ty1u7Iw/w34gQgDW9gfmtXRsYxiU6F0p9qZLfH0PXX +g/LVqEqOD+Zv1IzehfU3SiUpCwKBgQC5pgtZZAHvXLW6H74iGvISZ7/o/bd62/Or +AEP2t1Ja7JHk9MxmwNJ3Wa8JGjnZ2Pbd3xqVTqPddZFUfSgsNpGckJvuqWyqPYYU +/aYWEzrEypkh3ZbhCYE612Jp7SUwjWJgDqe0FdnzZKd/R6D4UIOFn2DKtnXbBqJO +N8GhMEHJqQKBgDWZM4tgfloyGvRIuIxr5sYhgAuTPQIEKaUPyBzxFK+4YWNMrtVL +E05LZ7WhjU/l1tsWwNqCEgZiWOEH60JmcYv2JZ06VwBF3nyIHHymm3tfhBpcAeEB +Pv/++DNaivDMTWQlgx+RtsQztJzihW2BZ9JMc/eqs+H4LAYpBqUYf9ynAoGAcD0J +A4RI/zPn0p06UFhGHgaHqg8qjKbKDIpejJyMt9fq5KdzpHPTSsD35+LpMuHPbphh +8/7VZyCbOp9oWEKtiiCLhaD8x3fmxm4LqbD6iNuL9UOI4ojijnaFU1FCeLYh0b1K +er/zQwmJkpP1p+rVeUXAOQ5S9pZuLifbct5AB/ECgYAgHXUpe6tqm7RjaQYCCYZa +153NAZbOVkgzsihllyR1QGMPrRYr56SyKQ9UX2AS3i4F0x5Cz8+uOFWhBAeZaGX1 +AYMPrzDUOVyEdPQyX0Xc8kWoYeUmhnm+QUSaF9XUUvSU4a0O8Vp4GqOvTGNUwgKi +fukZyfJQFc4AJhUFbSIv0g== +-----END PRIVATE KEY----- diff --git a/src/test/java/umc7th/bulk/record/service/RecordCompleteResetServiceTest.java b/src/test/java/umc7th/bulk/record/service/RecordCompleteResetServiceTest.java new file mode 100644 index 0000000..9b1ba55 --- /dev/null +++ b/src/test/java/umc7th/bulk/record/service/RecordCompleteResetServiceTest.java @@ -0,0 +1,48 @@ +package umc7th.bulk.record.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import umc7th.bulk.user.domain.User; +import umc7th.bulk.user.repository.UserRepository; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + + +class RecordCompleteResetServiceTest { + + @InjectMocks + private RecordCompleteResetService recordCompleteResetService; + + @Mock + private UserRepository userRepository; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testResetRecordComplete() { + // given + User user1 = User.builder().id(1L).email("user1@example.com").recordComplete(true).build(); + User user2 = User.builder().id(2L).email("user2@example.com").recordComplete(true).build(); + List users = Arrays.asList(user1, user2); + + // when + when(userRepository.resetRecordComplete()).thenReturn(2); + + recordCompleteResetService.resetRecordComplete(); + + // then + verify(userRepository, times(1)).resetRecordComplete(); // 1번 실행됐는지 검증 + } + + +} \ No newline at end of file diff --git a/src/test/java/umc7th/bulk/record/service/RecordServiceImplTest.java b/src/test/java/umc7th/bulk/record/service/RecordServiceImplTest.java new file mode 100644 index 0000000..60cc669 --- /dev/null +++ b/src/test/java/umc7th/bulk/record/service/RecordServiceImplTest.java @@ -0,0 +1,71 @@ +package umc7th.bulk.record.service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import umc7th.bulk.group.entity.Group; +import umc7th.bulk.group.repository.GroupRepository; +import umc7th.bulk.stageRecord.entity.StageRecord; +import umc7th.bulk.stageRecord.repository.StageRecordRepository; +import umc7th.bulk.user.domain.User; +import umc7th.bulk.user.repository.UserRepository; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class RecordServiceImplTest { + + @InjectMocks + private RecordServiceImpl recordService; // 테스트할 대상 + + @Mock + private UserRepository userRepository; + + @Mock + private StageRecordRepository stageRecordRepository; + + @Mock + private GroupRepository groupRepository; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + void testStageAdvancementWhenFiveUsersRecord() { + // given + Group group = Group.builder().groupId(1L).groupName("Test Group").currentStage(1).build(); + + User user1 = User.builder().id(1L).group(group).recordComplete(true).build(); + User user2 = User.builder().id(2L).group(group).recordComplete(true).build(); + User user3 = User.builder().id(3L).group(group).recordComplete(true).build(); + User user4 = User.builder().id(4L).group(group).recordComplete(true).build(); + User user5 = User.builder().id(5L).group(group).recordComplete(true).build(); + + StageRecord currentStage = StageRecord.builder() + .group(group) + .stageNumber(1) + .totalUsers(10) + .recordedUsers(4) // 현재 4명 기록 완료 (5명 달성 시 스테이지 변경) + .isCompleted(false) + .build(); + + // when + when(userRepository.countByGroupAndRecordCompleteTrue(group)).thenReturn(5); + when(stageRecordRepository.findTopByGroupOrderByStageNumberDesc(group)).thenReturn(Optional.of(currentStage)); + + recordService.checkAndAdvanceStage(group); + + // then + assertTrue(currentStage.isCompleted()); // ✅ 기존 스테이지가 완료됐는지 확인 + verify(stageRecordRepository, times(2)).save(any(StageRecord.class)); // ✅ 새로운 스테이지 저장 확인 + verify(userRepository, times(1)).countByGroupAndRecordCompleteTrue(group); // ✅ 기록 완료 인원 체크 확인 + verify(groupRepository, times(1)).save(group); + } + +} \ No newline at end of file