diff --git a/build.gradle b/build.gradle index 9b76ee5f..d007bfd6 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,12 @@ dependencies { testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.assertj:assertj-core' + // testcontainers + testImplementation 'org.springframework.boot:spring-boot-testcontainers' + testImplementation 'org.testcontainers:mysql:1.21.4' + testImplementation 'org.testcontainers:junit-jupiter:1.21.4' + testImplementation 'com.redis:testcontainers-redis:2.2.4' + // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0' diff --git a/src/main/java/umc/cockple/demo/domain/member/repository/MemberExerciseRepository.java b/src/main/java/umc/cockple/demo/domain/member/repository/MemberExerciseRepository.java index 51b25944..a3b798d9 100644 --- a/src/main/java/umc/cockple/demo/domain/member/repository/MemberExerciseRepository.java +++ b/src/main/java/umc/cockple/demo/domain/member/repository/MemberExerciseRepository.java @@ -8,6 +8,7 @@ import umc.cockple.demo.domain.member.domain.MemberExercise; import umc.cockple.demo.domain.member.enums.MemberStatus; +import java.time.LocalDate; import java.util.List; import java.util.Optional; @@ -35,4 +36,17 @@ List findByExerciseIdWithMemberAndProfile( "where me.member.id = :memberId and me.exercise.id in :exerciseIds") List findAllExerciseIdsByMemberAndExerciseIds(@Param("memberId") Long memberId, @Param("exerciseIds") List exerciseIds); + + @Query(""" + SELECT me.member.id, MAX(e.date) + FROM MemberExercise me + JOIN me.exercise e + WHERE me.member.id IN :memberIds + AND e.party.id = :partyId + AND e.date <= CURRENT_DATE + GROUP BY me.member.id + """) + List findLastExerciseDateByMemberIdsAndPartyId( + @Param("memberIds") List memberIds, + @Param("partyId") Long partyId); } diff --git a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java index bb21b4bb..c6d47ff9 100644 --- a/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java +++ b/src/main/java/umc/cockple/demo/domain/party/converter/PartyConverter.java @@ -13,9 +13,11 @@ import umc.cockple.demo.domain.party.enums.ParticipationType; import umc.cockple.demo.domain.party.enums.RequestStatus; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; @Component @@ -161,7 +163,7 @@ private List getLevelList(Party party, Gender gender) { return levelList.isEmpty() ? null : levelList; } - public PartyMemberDTO.Response toPartyMemberDTO(List memberParties, Long currentMemberId) { + public PartyMemberDTO.Response toPartyMemberDTO(List memberParties, Long currentMemberId, Map lastExerciseDateMap) { //멤버 리스트 생성 List memberDetails = memberParties.stream() .map(mp -> { @@ -174,6 +176,7 @@ public PartyMemberDTO.Response toPartyMemberDTO(List memberParties, .gender(member.getGender().name()) .level(member.getLevel().getKoreanName()) .isMe(member.getId().equals(currentMemberId)) + .lastExerciseDate(lastExerciseDateMap.get(member.getId())) .build(); }) //Role에 따라 정렬 (모임장, 부모임장이 위로 가도록 정렬) diff --git a/src/main/java/umc/cockple/demo/domain/party/dto/PartyMemberDTO.java b/src/main/java/umc/cockple/demo/domain/party/dto/PartyMemberDTO.java index 152a6993..f6bb048f 100644 --- a/src/main/java/umc/cockple/demo/domain/party/dto/PartyMemberDTO.java +++ b/src/main/java/umc/cockple/demo/domain/party/dto/PartyMemberDTO.java @@ -2,6 +2,7 @@ import lombok.Builder; +import java.time.LocalDate; import java.util.List; public class PartyMemberDTO { @@ -26,6 +27,7 @@ public record MemberDetail( String role, String gender, String level, - Boolean isMe + Boolean isMe, + LocalDate lastExerciseDate ) {} } diff --git a/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java b/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java index 0dd5a7da..81424b9a 100644 --- a/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java +++ b/src/main/java/umc/cockple/demo/domain/party/service/PartyQueryServiceImpl.java @@ -13,6 +13,7 @@ import umc.cockple.demo.domain.member.exception.MemberErrorCode; import umc.cockple.demo.domain.member.exception.MemberException; import umc.cockple.demo.domain.member.repository.MemberAddrRepository; +import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; import umc.cockple.demo.domain.member.repository.MemberPartyRepository; import umc.cockple.demo.domain.member.repository.MemberRepository; import umc.cockple.demo.domain.party.converter.PartyConverter; @@ -30,6 +31,7 @@ import umc.cockple.demo.domain.party.repository.PartyRepository; import umc.cockple.demo.global.enums.*; +import java.time.LocalDate; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.*; @@ -48,6 +50,7 @@ public class PartyQueryServiceImpl implements PartyQueryService{ private final MemberPartyRepository memberPartyRepository; private final MemberAddrRepository memberAddrRepository; private final ExerciseRepository exerciseRepository; + private final MemberExerciseRepository memberExerciseRepository; private final PartyBookmarkRepository partyBookmarkRepository; private final ImageService imageService; @@ -133,8 +136,11 @@ public PartyMemberDTO.Response getPartyMembers(Long partyId, Long currentMemberI //모임 멤버 목록 조회 List memberParties = memberPartyRepository.findAllByPartyIdWithMember(partyId); + //멤버별 마지막 운동일 조회 + Map lastExerciseDateMap = getLastExerciseDateMap(memberParties, partyId); + log.info("모임 멤버 목록 조회 완료 - partyId: {}", partyId); - return partyConverter.toPartyMemberDTO(memberParties, currentMemberId); + return partyConverter.toPartyMemberDTO(memberParties, currentMemberId, lastExerciseDateMap); } @Override @@ -216,6 +222,15 @@ private Member findMemberOrThrow(Long memberId) { .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND)); } + //멤버별 마지막 운동일 조회 + private Map getLastExerciseDateMap(List memberParties, Long partyId) { + List memberIds = memberParties.stream().map(mp -> mp.getMember().getId()).toList(); + return memberExerciseRepository + .findLastExerciseDateByMemberIdsAndPartyId(memberIds, partyId) + .stream() + .collect(Collectors.toMap(row -> (Long) row[0], row -> (LocalDate) row[1])); + } + //운동 정보 조회 private ExerciseInfo getExerciseInfo(List partyIds) { //운동 개수 정보 조회 diff --git a/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java new file mode 100644 index 00000000..87f8f824 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/party/integration/PartyIntegrationTest.java @@ -0,0 +1,130 @@ +package umc.cockple.demo.domain.party.integration; + +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.web.servlet.MockMvc; +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.exercise.repository.ExerciseRepository; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; +import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.member.repository.MemberRepository; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.exception.PartyErrorCode; +import umc.cockple.demo.domain.party.repository.PartyAddrRepository; +import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.global.enums.Gender; +import umc.cockple.demo.global.enums.Level; +import umc.cockple.demo.global.enums.Role; +import umc.cockple.demo.support.IntegrationTestBase; +import umc.cockple.demo.support.SecurityContextHelper; +import umc.cockple.demo.support.fixture.ExerciseFixture; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.time.LocalDate; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +class PartyIntegrationTest extends IntegrationTestBase { + + @Autowired MockMvc mockMvc; + @Autowired MemberRepository memberRepository; + @Autowired PartyRepository partyRepository; + @Autowired MemberPartyRepository memberPartyRepository; + @Autowired PartyAddrRepository partyAddrRepository; + @Autowired ExerciseRepository exerciseRepository; + @Autowired MemberExerciseRepository memberExerciseRepository; + + private Member manager; + private Member normalMember; + private Party party; + + @BeforeEach + void setUp() { + manager = memberRepository.save(MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L)); + normalMember = memberRepository.save(MemberFixture.createMember("일반멤버", Gender.FEMALE, Level.B, 1002L)); + + PartyAddr addr = partyAddrRepository.save(PartyFixture.createPartyAddr("서울특별시", "강남구")); + party = partyRepository.save(PartyFixture.createParty("테스트 모임", manager.getId(), addr)); + + memberPartyRepository.save(MemberFixture.createMemberParty(party, manager, Role.party_MANAGER)); + memberPartyRepository.save(MemberFixture.createMemberParty(party, normalMember, Role.party_MEMBER)); + + SecurityContextHelper.setAuthentication(manager.getId(), manager.getNickname()); + } + + @AfterEach + void tearDown() { + memberExerciseRepository.deleteAll(); + exerciseRepository.deleteAll(); + memberPartyRepository.deleteAll(); + partyRepository.deleteAll(); + partyAddrRepository.deleteAll(); + memberRepository.deleteAll(); + } + + @Nested + @DisplayName("GET /api/parties/{partyId}/members - 모임 멤버 조회") + class GetPartyMembers { + + @Test + @DisplayName("200 - 멤버 목록과 마지막 운동일을 정상 반환한다") + void success_withLastExerciseDate() throws Exception { + Exercise exercise = exerciseRepository.save( + ExerciseFixture.createExercise(party, LocalDate.of(2025, 1, 10))); + memberExerciseRepository.save(MemberFixture.createMemberExercise(normalMember, exercise)); + + mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.summary.totalCount").value(2)) + .andExpect(jsonPath("$.data.summary.maleCount").value(1)) + .andExpect(jsonPath("$.data.summary.femaleCount").value(1)) + // 첫 번째 멤버(매니저) 전체 필드 검증 + .andExpect(jsonPath("$.data.members[0].memberId").value(manager.getId())) + .andExpect(jsonPath("$.data.members[0].nickname").value("매니저")) + .andExpect(jsonPath("$.data.members[0].profileImageUrl").doesNotExist()) + .andExpect(jsonPath("$.data.members[0].role").value("party_MANAGER")) + .andExpect(jsonPath("$.data.members[0].gender").value("MALE")) + .andExpect(jsonPath("$.data.members[0].level").value("A조")) + .andExpect(jsonPath("$.data.members[0].isMe").value(true)) + .andExpect(jsonPath("$.data.members[0].lastExerciseDate").doesNotExist()) + // 두 번째 멤버(일반멤버) 마지막 운동일 검증 + .andExpect(jsonPath("$.data.members[1].lastExerciseDate").value("2025-01-10")); + } + + @Test + @DisplayName("200 - 운동 기록이 없는 멤버의 lastExerciseDate는 null이다") + void success_noExerciseHistory() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.summary.totalCount").value(2)) + .andExpect(jsonPath("$.data.members[0].lastExerciseDate").isEmpty()) + .andExpect(jsonPath("$.data.members[1].lastExerciseDate").isEmpty()); + } + + @Test + @DisplayName("404 - 존재하지 않는 파티면 에러를 반환한다") + void fail_partyNotFound() throws Exception { + mockMvc.perform(get("/api/parties/{partyId}/members", 999L)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_NOT_FOUND.getCode())) + .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_NOT_FOUND.getMessage())); + } + + @Test + @DisplayName("400 - 비활성화된 파티면 에러를 반환한다") + void fail_partyInactive() throws Exception { + party.delete(); + partyRepository.save(party); + + mockMvc.perform(get("/api/parties/{partyId}/members", party.getId())) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(PartyErrorCode.PARTY_IS_DELETED.getCode())) + .andExpect(jsonPath("$.message").value(PartyErrorCode.PARTY_IS_DELETED.getMessage())); + } + } +} diff --git a/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java new file mode 100644 index 00000000..5f6a9005 --- /dev/null +++ b/src/test/java/umc/cockple/demo/domain/party/service/PartyQueryServiceTest.java @@ -0,0 +1,170 @@ +package umc.cockple.demo.domain.party.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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 org.springframework.test.util.ReflectionTestUtils; +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.repository.MemberExerciseRepository; +import umc.cockple.demo.domain.member.repository.MemberPartyRepository; +import umc.cockple.demo.domain.party.converter.PartyConverter; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.dto.PartyMemberDTO; +import umc.cockple.demo.domain.party.exception.PartyErrorCode; +import umc.cockple.demo.domain.party.exception.PartyException; +import umc.cockple.demo.domain.party.repository.PartyRepository; +import umc.cockple.demo.global.enums.Gender; +import umc.cockple.demo.global.enums.Level; +import umc.cockple.demo.global.enums.Role; +import umc.cockple.demo.support.fixture.MemberFixture; +import umc.cockple.demo.support.fixture.PartyFixture; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class PartyQueryServiceTest { + + @InjectMocks + private PartyQueryServiceImpl partyQueryService; + + @Mock + private PartyRepository partyRepository; + @Mock + private PartyConverter partyConverter; + @Mock + private MemberPartyRepository memberPartyRepository; + @Mock + private MemberExerciseRepository memberExerciseRepository; + + @Nested + @DisplayName("getPartyMembers") + class GetPartyMembers { + + @Test + @DisplayName("멤버 목록과 마지막 운동일을 함께 반환한다") + void success() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + Member member1 = MemberFixture.createMember("멤버1", Gender.FEMALE, Level.A, 1002L); + ReflectionTestUtils.setField(manager, "id", 10L); + ReflectionTestUtils.setField(member1, "id", 20L); + + MemberParty mp1 = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + MemberParty mp2 = MemberFixture.createMemberParty(party, member1, Role.party_MEMBER); + List memberParties = List.of(mp1, mp2); + + LocalDate lastDate = LocalDate.of(2025, 1, 10); + List rawResult = List.of(new Object[]{20L, lastDate}); + + PartyMemberDTO.Response expected = PartyMemberDTO.Response.builder() + .summary(PartyMemberDTO.Summary.builder() + .totalCount(2).maleCount(1).femaleCount(1).build()) + .members(List.of()) + .build(); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId)).willReturn(rawResult); + given(partyConverter.toPartyMemberDTO(eq(memberParties), eq(currentMemberId), any())) + .willReturn(expected); + + // when + PartyMemberDTO.Response result = partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + assertThat(result).isEqualTo(expected); + verify(memberExerciseRepository).findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L, 20L), partyId); + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of(20L, lastDate)) + ); + } + + @Test + @DisplayName("운동 기록이 없는 멤버는 빈 Map이 converter에 전달된다") + void noExerciseHistory() { + // given + Long partyId = 1L; + Long currentMemberId = 10L; + + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party party = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(party, "id", partyId); + Member manager = MemberFixture.createMember("매니저", Gender.MALE, Level.A, 1001L); + ReflectionTestUtils.setField(manager, "id", 10L); + MemberParty mp = MemberFixture.createMemberParty(party, manager, Role.party_MANAGER); + List memberParties = List.of(mp); + + given(partyRepository.findById(partyId)).willReturn(Optional.of(party)); + given(memberPartyRepository.findAllByPartyIdWithMember(partyId)).willReturn(memberParties); + given(memberExerciseRepository.findLastExerciseDateByMemberIdsAndPartyId( + List.of(10L), partyId)).willReturn(List.of()); + given(partyConverter.toPartyMemberDTO(any(), any(), any())).willReturn(null); + + // when + partyQueryService.getPartyMembers(partyId, currentMemberId); + + // then + verify(partyConverter).toPartyMemberDTO( + eq(memberParties), + eq(currentMemberId), + eq(Map.of()) + ); + } + + @Test + @DisplayName("존재하지 않는 파티면 PartyException을 던진다") + void partyNotFound() { + // given + given(partyRepository.findById(99L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(99L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_NOT_FOUND)); + } + + @Test + @DisplayName("비활성화된 파티면 PartyException을 던진다") + void partyInactive() { + // given + PartyAddr addr = PartyFixture.createPartyAddr("서울특별시", "강남구"); + Party inactiveParty = PartyFixture.createParty("테스트 모임", 10L, addr); + ReflectionTestUtils.setField(inactiveParty, "id", 1L); + inactiveParty.delete(); + + given(partyRepository.findById(1L)).willReturn(Optional.of(inactiveParty)); + + // when & then + assertThatThrownBy(() -> partyQueryService.getPartyMembers(1L, 1L)) + .isInstanceOf(PartyException.class) + .satisfies(e -> assertThat(((PartyException) e).getCode()).isEqualTo(PartyErrorCode.PARTY_IS_DELETED)); + } + } + +} diff --git a/src/test/java/umc/cockple/demo/support/IntegrationTestBase.java b/src/test/java/umc/cockple/demo/support/IntegrationTestBase.java new file mode 100644 index 00000000..764fe06c --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/IntegrationTestBase.java @@ -0,0 +1,15 @@ +package umc.cockple.demo.support; + +import org.junit.jupiter.api.Tag; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; + +@Tag("integration") +@SpringBootTest +@AutoConfigureMockMvc +@Import(IntegrationTestConfig.class) +@ActiveProfiles("integrationtest") +public abstract class IntegrationTestBase { +} diff --git a/src/test/java/umc/cockple/demo/support/IntegrationTestConfig.java b/src/test/java/umc/cockple/demo/support/IntegrationTestConfig.java new file mode 100644 index 00000000..e245f83e --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/IntegrationTestConfig.java @@ -0,0 +1,35 @@ +package umc.cockple.demo.support; + +import com.redis.testcontainers.RedisContainer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.testcontainers.service.connection.ServiceConnection; +import org.springframework.context.annotation.Bean; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.utility.DockerImageName; + +@TestConfiguration(proxyBeanMethods = false) +public class IntegrationTestConfig { + + private static final MySQLContainer mysql = + new MySQLContainer<>("mysql:8.0.36"); + + private static final RedisContainer redis = + new RedisContainer(DockerImageName.parse("redis:7.2-alpine")); + + static { + mysql.start(); + redis.start(); + } + + @Bean + @ServiceConnection + MySQLContainer mySQLContainer() { + return mysql; + } + + @Bean + @ServiceConnection + RedisContainer redisContainer() { + return redis; + } +} diff --git a/src/test/java/umc/cockple/demo/support/SecurityContextHelper.java b/src/test/java/umc/cockple/demo/support/SecurityContextHelper.java new file mode 100644 index 00000000..d6b3712e --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/SecurityContextHelper.java @@ -0,0 +1,19 @@ +package umc.cockple.demo.support; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import umc.cockple.demo.global.security.domain.CustomUserDetails; + +public class SecurityContextHelper { + + public static void setAuthentication(Long memberId, String nickname) { + CustomUserDetails userDetails = new CustomUserDetails(memberId, nickname); + UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(auth); + } + + public static void clearAuthentication() { + SecurityContextHolder.clearContext(); + } +} diff --git a/src/test/java/umc/cockple/demo/support/fixture/ExerciseFixture.java b/src/test/java/umc/cockple/demo/support/fixture/ExerciseFixture.java new file mode 100644 index 00000000..de2cff21 --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/fixture/ExerciseFixture.java @@ -0,0 +1,21 @@ +package umc.cockple.demo.support.fixture; + +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.party.domain.Party; + +import java.time.LocalDate; +import java.time.LocalTime; + +public class ExerciseFixture { + + public static Exercise createExercise(Party party, LocalDate date) { + return Exercise.builder() + .party(party) + .date(date) + .startTime(LocalTime.of(10, 0)) + .maxCapacity(10) + .partyGuestAccept(true) + .outsideGuestAccept(false) + .build(); + } +} diff --git a/src/test/java/umc/cockple/demo/support/fixture/MemberFixture.java b/src/test/java/umc/cockple/demo/support/fixture/MemberFixture.java new file mode 100644 index 00000000..c14c1cf2 --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/fixture/MemberFixture.java @@ -0,0 +1,46 @@ +package umc.cockple.demo.support.fixture; + +import umc.cockple.demo.domain.member.domain.Member; +import umc.cockple.demo.domain.member.domain.MemberExercise; +import umc.cockple.demo.domain.member.domain.MemberParty; +import umc.cockple.demo.domain.member.enums.MemberPartyStatus; +import umc.cockple.demo.domain.member.enums.MemberStatus; +import umc.cockple.demo.domain.exercise.domain.Exercise; +import umc.cockple.demo.domain.exercise.enums.ExerciseMemberShipStatus; +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.global.enums.Gender; +import umc.cockple.demo.global.enums.Level; +import umc.cockple.demo.global.enums.Role; + +import java.time.LocalDateTime; + +public class MemberFixture { + + public static Member createMember(String nickname, Gender gender, Level level, Long socialId) { + return Member.builder() + .nickname(nickname) + .gender(gender) + .level(level) + .isActive(MemberStatus.ACTIVE) + .socialId(socialId) + .build(); + } + + public static MemberParty createMemberParty(Party party, Member member, Role role) { + return MemberParty.builder() + .party(party) + .member(member) + .role(role) + .joinedAt(LocalDateTime.now()) + .status(MemberPartyStatus.ACTIVE) + .build(); + } + + public static MemberExercise createMemberExercise(Member member, Exercise exercise) { + return MemberExercise.builder() + .member(member) + .exercise(exercise) + .exerciseMemberShipStatus(ExerciseMemberShipStatus.PARTY_MEMBER) + .build(); + } +} diff --git a/src/test/java/umc/cockple/demo/support/fixture/PartyFixture.java b/src/test/java/umc/cockple/demo/support/fixture/PartyFixture.java new file mode 100644 index 00000000..2696bb27 --- /dev/null +++ b/src/test/java/umc/cockple/demo/support/fixture/PartyFixture.java @@ -0,0 +1,34 @@ +package umc.cockple.demo.support.fixture; + +import umc.cockple.demo.domain.party.domain.Party; +import umc.cockple.demo.domain.party.domain.PartyAddr; +import umc.cockple.demo.domain.party.enums.ActivityTime; +import umc.cockple.demo.domain.party.enums.ParticipationType; +import umc.cockple.demo.domain.party.enums.PartyStatus; + +public class PartyFixture { + + public static PartyAddr createPartyAddr(String addr1, String addr2) { + return PartyAddr.builder() + .addr1(addr1) + .addr2(addr2) + .build(); + } + + public static Party createParty(String partyName, Long ownerId, PartyAddr addr) { + return Party.builder() + .partyName(partyName) + .ownerId(ownerId) + .partyAddr(addr) + .partyType(ParticipationType.MIX_DOUBLES) + .minBirthYear(1990) + .maxBirthYear(2005) + .price(10000) + .joinPrice(5000) + .designatedCock("욘넥스") + .activityTime(ActivityTime.MORNING) + .status(PartyStatus.ACTIVE) + .exerciseCount(0) + .build(); + } +} diff --git a/src/test/resources/application-integrationtest.yml b/src/test/resources/application-integrationtest.yml new file mode 100644 index 00000000..ab4deefc --- /dev/null +++ b/src/test/resources/application-integrationtest.yml @@ -0,0 +1,51 @@ +# Testcontainers가 datasource/redis를 @ServiceConnection으로 자동 주입하므로 별도 설정 불필요 +spring: + data: + redis: + host: localhost + port: 6379 + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + show_sql: true + format_sql: true + + cache: + type: redis + +cloud: + aws: + s3: + bucket: test-bucket + credentials: + access-key: test-access-key + secret-key: test-secret-key + region: + static: ap-northeast-2 + stack: + auto: false + +kakao: + client-id: test-client-id + client-secret: test-client-secret + token-uri: https://kauth.kakao.com/oauth/token + user-info-uri: https://kapi.kakao.com/v2/user/me + redirect-uri: http://localhost/login/kakao + admin-key: test-admin-key + unlink-uri: https://kapi.kakao.com/v1/user/unlink + +jwt: + secret: dGVzdC1zZWNyZXQta2V5LWZvci1pbnRlZ3JhdGlvbi10ZXN0LWxvbmctZW5vdWdoLTI1Ng + access-token-validity: 3600000 + refresh-token-validity: 1209600000 + +logging: + level: + com.techfork: DEBUG + org.springframework.batch: INFO + org.hibernate.SQL: DEBUG + org.hibernate.tool.schema: ERROR \ No newline at end of file