diff --git a/src/main/java/com/ajou/hertz/common/entity/BaseEntity.java b/src/main/java/com/ajou/hertz/common/entity/BaseEntity.java index ea4463a..8735100 100644 --- a/src/main/java/com/ajou/hertz/common/entity/BaseEntity.java +++ b/src/main/java/com/ajou/hertz/common/entity/BaseEntity.java @@ -1,31 +1,32 @@ package com.ajou.hertz.common.entity; +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + import jakarta.persistence.Column; import jakarta.persistence.MappedSuperclass; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.LastModifiedBy; - -import java.time.LocalDateTime; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @MappedSuperclass public abstract class BaseEntity extends TimeTrackedBaseEntity { - @CreatedBy - @Column(nullable = false, updatable = false) - protected Long createdBy; + @CreatedBy + @Column(nullable = false, updatable = false) + protected Long createdBy; - @LastModifiedBy - @Column(nullable = false) - protected Long updatedBy; + @LastModifiedBy + @Column(nullable = false) + protected Long updatedBy; - protected BaseEntity(LocalDateTime createdAt, LocalDateTime updatedAt, Long createdBy, Long updatedBy) { - super(createdAt, updatedAt); - this.createdBy = createdBy; - this.updatedBy = updatedBy; - } + protected BaseEntity(LocalDateTime createdAt, LocalDateTime updatedAt, Long createdBy, Long updatedBy) { + super(createdAt, updatedAt); + this.createdBy = createdBy; + this.updatedBy = updatedBy; + } } diff --git a/src/main/java/com/ajou/hertz/common/entity/FullAddress.java b/src/main/java/com/ajou/hertz/common/entity/FullAddress.java index 205e054..f7584ae 100644 --- a/src/main/java/com/ajou/hertz/common/entity/FullAddress.java +++ b/src/main/java/com/ajou/hertz/common/entity/FullAddress.java @@ -11,7 +11,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @Embeddable diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java index 6df24cf..7aab72c 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/PracticeRoomDto.java @@ -12,7 +12,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -@AllArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class PracticeRoomDto { @@ -21,6 +21,7 @@ public class PracticeRoomDto { private UserDto seller; private String title; private FullAddressDto tradeAddress; + private CoordinateDto coordinate; private Boolean hasSoundEquipment; private Boolean hasInstrument; private Integer pricePerDay; @@ -30,18 +31,16 @@ public class PracticeRoomDto { private String size; private Boolean hasParkingLot; private String description; - private CoordinateDto coordinate; private List images; private List hashtags; public PracticeRoomDto(PracticeRoom practiceRoom) { this(practiceRoom.getId(), UserDto.from(practiceRoom.getSeller()), practiceRoom.getTitle(), - FullAddressDto.from(practiceRoom.getTradeAddress()), practiceRoom.getHasSoundEquipment(), - practiceRoom.getHasInstrument(), practiceRoom.getPricePerDay(), practiceRoom.getPricePerHour(), - practiceRoom.getPricePerMonth(), practiceRoom.getCapacity(), practiceRoom.getSize(), - practiceRoom.getHasParkingLot(), practiceRoom.getDescription().getDescription(), - CoordinateDto.from(practiceRoom.getCoordinate()), practiceRoom.getImages().toDtos(), - practiceRoom.getHashtags().toStrings()); + FullAddressDto.from(practiceRoom.getTradeAddress()), CoordinateDto.from(practiceRoom.getCoordinate()), + practiceRoom.getHasSoundEquipment(), practiceRoom.getHasInstrument(), practiceRoom.getPricePerDay(), + practiceRoom.getPricePerHour(), practiceRoom.getPricePerMonth(), practiceRoom.getCapacity(), + practiceRoom.getSize(), practiceRoom.getHasParkingLot(), practiceRoom.getDescription().getDescription(), + practiceRoom.getImages().toDtos(), practiceRoom.getHashtags().toStrings()); } public static PracticeRoomDto from(PracticeRoom practiceRoom) { diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java b/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java index e4a8afb..cd6da4b 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/dto/request/CreateNewPracticeRoomRequest.java @@ -81,9 +81,41 @@ public class CreateNewPracticeRoomRequest { private List<@NotBlank @Length(max = 10) String> hashtags; public PracticeRoom toEntity(User seller) { - return PracticeRoom.create(seller, getTitle(), getDescription(), getCapacity(), getSize(), - getHasSoundEquipment(), getHasInstrument(), getPricePerHour(), getPricePerDay(), getPricePerMonth(), - getHasParkingLot(), getTradeAddress().toEntity(), getCoordinate().toEntity()); + return PracticeRoom.create(seller, getTitle(), getTradeAddress().toEntity(), getCoordinate().toEntity(), + getHasSoundEquipment(), getHasInstrument(), getPricePerDay(), getPricePerHour(), getPricePerMonth(), + getCapacity(), getSize(), getHasParkingLot(), getDescription()); + } + + public CreateNewPracticeRoomRequest( + String title, + FullAddressRequest tradeAddress, + CoordinateRequest coordinate, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerDay, + Integer pricePerHour, + Integer pricePerMonth, + Short capacity, + String size, + Boolean hasParkingLot, + String description, + List images, + List hashtags + ) { + this.title = title; + this.tradeAddress = tradeAddress; + this.coordinate = coordinate; + this.hasSoundEquipment = hasSoundEquipment; + this.hasInstrument = hasInstrument; + this.pricePerDay = pricePerDay; + this.pricePerHour = pricePerHour; + this.pricePerMonth = pricePerMonth; + this.capacity = capacity; + this.size = size; + this.hasParkingLot = hasParkingLot; + this.description = description; + this.images = images; + this.hashtags = hashtags; } } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java index 313fcde..9e4bf96 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/entity/PracticeRoom.java @@ -82,6 +82,7 @@ protected PracticeRoom( User seller, String title, FullAddress tradeAddress, + Coordinate coordinate, Boolean hasSoundEquipment, Boolean hasInstrument, Integer pricePerDay, @@ -90,14 +91,13 @@ protected PracticeRoom( Short capacity, String size, Boolean hasParkingLot, - String description, - Coordinate coordinate - + String description ) { this.id = id; this.seller = seller; this.title = title; this.tradeAddress = tradeAddress; + this.coordinate = coordinate; this.hasSoundEquipment = hasSoundEquipment; this.hasInstrument = hasInstrument; this.pricePerDay = pricePerDay; @@ -107,29 +107,31 @@ protected PracticeRoom( this.size = size; this.hasParkingLot = hasParkingLot; this.description = new PracticeRoomDescription(description); - this.coordinate = coordinate; + } public static PracticeRoom create( User seller, String title, - String description, - Short capacity, - String size, + FullAddress tradeAddress, + Coordinate coordinate, Boolean hasSoundEquipment, Boolean hasInstrument, Integer pricePerHour, Integer pricePerDay, Integer pricePerMonth, + Short capacity, + String size, Boolean hasParkingLot, - FullAddress tradeAddress, - Coordinate coordinate + String description + ) { return new PracticeRoom( null, seller, title, tradeAddress, + coordinate, hasSoundEquipment, hasInstrument, pricePerDay, @@ -138,8 +140,7 @@ public static PracticeRoom create( capacity, size, hasParkingLot, - description, - coordinate + description ); } } diff --git a/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java index d75a928..643a48c 100644 --- a/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java +++ b/src/main/java/com/ajou/hertz/domain/practice_room/service/PracticeRoomCommandService.java @@ -50,8 +50,6 @@ public PracticeRoomDto createNewPracticeRoom(Long sellerId, ); practiceRoom.getHashtags().addAll(savedPracticeRoomHashtags); - PracticeRoom updatedPracticeRoom = practiceRoomRepository.save(practiceRoom); - - return new PracticeRoomDto(updatedPracticeRoom); + return new PracticeRoomDto(practiceRoom); } } \ No newline at end of file diff --git a/src/test/java/com/ajou/hertz/unit/domain/practice_room/controller/PracticeRoomControllerTest.java b/src/test/java/com/ajou/hertz/unit/domain/practice_room/controller/PracticeRoomControllerTest.java new file mode 100644 index 0000000..ba00f3d --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/practice_room/controller/PracticeRoomControllerTest.java @@ -0,0 +1,219 @@ +package com.ajou.hertz.unit.domain.practice_room.controller; + +import static com.ajou.hertz.common.constant.GlobalConstants.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.web.servlet.MockMvc; + +import com.ajou.hertz.common.auth.UserPrincipal; +import com.ajou.hertz.common.dto.CoordinateDto; +import com.ajou.hertz.common.dto.FullAddressDto; +import com.ajou.hertz.common.dto.request.CoordinateRequest; +import com.ajou.hertz.common.dto.request.FullAddressRequest; +import com.ajou.hertz.config.ControllerTestConfig; +import com.ajou.hertz.domain.practice_room.controller.PracticeRoomController; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomImageDto; +import com.ajou.hertz.domain.practice_room.dto.request.CreateNewPracticeRoomRequest; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomCommandService; +import com.ajou.hertz.domain.user.constant.Gender; +import com.ajou.hertz.domain.user.constant.RoleType; +import com.ajou.hertz.domain.user.dto.UserDto; +import com.ajou.hertz.util.ReflectionUtils; + +@DisplayName("[Unit] Controller - PracticeRoom") +@Import(ControllerTestConfig.class) +@WebMvcTest(controllers = PracticeRoomController.class) +public class PracticeRoomControllerTest { + private final MockMvc mvc; + @MockBean + private PracticeRoomCommandService practiceRoomCommandService; + + @Autowired + public PracticeRoomControllerTest(MockMvc mvc) { + this.mvc = mvc; + } + + @Test + void 새로_등록할_합주실의_정보가_주어지면_합주실_매물을_등록한다() throws Exception { + // given + long sellerId = 1L; + CreateNewPracticeRoomRequest practiceRoomRequest = createNewPracticeRoomRequest(); + PracticeRoomDto expectedResult = createPracticeRoomDto(2L, sellerId); + given(practiceRoomCommandService.createNewPracticeRoom( + eq(sellerId), any(CreateNewPracticeRoomRequest.class) + )).willReturn(expectedResult); + + // when & then + mvc.perform( + multipart("/api/practice-rooms") + .file("images[0]", practiceRoomRequest.getImages().get(0).getBytes()) + .file("images[1]", practiceRoomRequest.getImages().get(1).getBytes()) + .file("images[2]", practiceRoomRequest.getImages().get(2).getBytes()) + .file("images[3]", practiceRoomRequest.getImages().get(3).getBytes()) + .header(API_VERSION_HEADER_NAME, 1) + .param("title", practiceRoomRequest.getTitle()) + .param("tradeAddress.fullAddress", practiceRoomRequest.getTradeAddress().getFullAddress()) + .param("tradeAddress.detailAddress", practiceRoomRequest.getTradeAddress().getDetailAddress()) + .param("coordinate.lat", practiceRoomRequest.getCoordinate().getLat()) + .param("coordinate.lng", practiceRoomRequest.getCoordinate().getLng()) + .param("hasSoundEquipment", String.valueOf(practiceRoomRequest.getHasSoundEquipment())) + .param("hasInstrument", String.valueOf(practiceRoomRequest.getHasInstrument())) + .param("pricePerDay", String.valueOf(practiceRoomRequest.getPricePerDay())) + .param("pricePerHour", String.valueOf(practiceRoomRequest.getPricePerHour())) + .param("pricePerMonth", String.valueOf(practiceRoomRequest.getPricePerMonth())) + .param("capacity", String.valueOf(practiceRoomRequest.getCapacity())) + .param("size", String.valueOf(practiceRoomRequest.getSize())) + .param("hasParkingLot", String.valueOf(practiceRoomRequest.getHasParkingLot())) + .param("description", practiceRoomRequest.getDescription()) + .with(user(createTestUser(sellerId))) + ) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.id").value(expectedResult.getId())) + .andExpect(jsonPath("$.sellerId").value(sellerId)) + .andExpect(jsonPath("$.images").isArray()) + .andExpect(jsonPath("$.images.size()").value(expectedResult.getImages().size())) + .andExpect(jsonPath("$.hashtags").isArray()) + .andExpect(jsonPath("$.hashtags.size()").value(expectedResult.getHashtags().size())); + then(practiceRoomCommandService).should().createNewPracticeRoom( + eq(sellerId), any(CreateNewPracticeRoomRequest.class) + ); + verifyEveryMocksShouldHaveNoMoreInteractions(); + } + + private void verifyEveryMocksShouldHaveNoMoreInteractions() { + then(practiceRoomCommandService).shouldHaveNoMoreInteractions(); + } + + private UserDetails createTestUser(Long userId) throws Exception { + return new UserPrincipal(createUserDto(userId)); + } + + private MockMultipartFile createMultipartFile() { + return new MockMultipartFile( + "image", + "originalFilename", + MediaType.IMAGE_PNG_VALUE, + "content".getBytes() + ); + } + + private UserDto createUserDto(long id) throws Exception { + return ReflectionUtils.createUserDto( + id, + Set.of(RoleType.USER), + "test@mail.com", + "$2a$abc123", + "kakao-user-id", + "https://user-default-profile-image", + LocalDate.of(2024, 1, 1), + Gender.ETC, + "01012345678", + "https://contack-link", + LocalDateTime.of(2024, 1, 1, 0, 0) + ); + } + + private PracticeRoomImageDto createPracticeRoomImageDto(long practiceRoomImageId) throws Exception { + return ReflectionUtils.createPracticeRoomImageDto( + practiceRoomImageId, + "image-name", + "https://practiceRoom-image-url" + ); + } + + private FullAddressRequest createFullAddressRequest() throws Exception { + return ReflectionUtils.createFullAddressRequest( + "서울특별시 강남구 청담동 123-456", + "아주빌딩 2층" + ); + } + + private FullAddressDto createFullAddressDto() throws Exception { + return ReflectionUtils.createFullAddressDto( + "서울특별시", + "강남구", + "서울특별시 강남구 청담동 123-456", + "null", + "아주빌딩 2층" + ); + } + + private CoordinateRequest createCoordinateRequest() throws Exception { + return ReflectionUtils.createCoordinateRequest( + "37.123456", + "127.123456" + ); + } + + private CoordinateDto createCoordinateDto() throws Exception { + return ReflectionUtils.createCoordinateDto( + "37.123456", + "127.123456" + ); + } + + private CreateNewPracticeRoomRequest createNewPracticeRoomRequest() throws Exception { + return ReflectionUtils.createNewPracticeRoomRequest( + "Title", + createFullAddressRequest(), + createCoordinateRequest(), + true, + true, + 100, + 1000, + 10000, + (short)3, + "100", + true, + "description", + List.of(createMultipartFile(), createMultipartFile(), createMultipartFile(), createMultipartFile()), + List.of("Band", "instrument") + ); + } + + private PracticeRoomDto createPracticeRoomDto(long id, long sellerId) throws Exception { + return ReflectionUtils.createPracticeRoomDto( + id, + createUserDto(sellerId), + "Title", + createFullAddressDto(), + createCoordinateDto(), + true, + true, + 10, + 100, + 1000, + (short)3, + "100평", + true, + "description", + List.of( + createPracticeRoomImageDto(id + 1), + createPracticeRoomImageDto(id + 2), + createPracticeRoomImageDto(id + 3), + createPracticeRoomImageDto(id + 4) + ), + List.of("Band", "instrument") + ); + } + +} diff --git a/src/test/java/com/ajou/hertz/unit/domain/practice_room/entity/PracticeRoomDescriptionTest.java b/src/test/java/com/ajou/hertz/unit/domain/practice_room/entity/PracticeRoomDescriptionTest.java new file mode 100644 index 0000000..ecd6b66 --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/practice_room/entity/PracticeRoomDescriptionTest.java @@ -0,0 +1,47 @@ +package com.ajou.hertz.unit.domain.practice_room.entity; + +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomDescription; +import com.ajou.hertz.domain.practice_room.exception.UnexpectedLinkInPracticeRoomDescriptionException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +public class PracticeRoomDescriptionTest { + @ParameterizedTest + @ValueSource(strings = { + "Practice rooms are equipped with soundproofing to ensure musicians can practice without disturbing others.", + "In a practice room, musicians can hone their skills and perfect their technique in a private setting.", + "Practice rooms often feature mirrors to allow musicians to observe their posture and technique while playing.", + "Practice rooms may contain specialized equipment such as metronomes and music stands to aid musicians in their practice sessions.", + "In a practice room, musicians can experiment with different instruments and musical styles to broaden their repertoire." + }) + void 합주실_설명에_링크가_존재하지_않는다면_정상적으로_객체가_생성된다(String description) { + // given + + // when + PracticeRoomDescription PracticeRoomDescription = new PracticeRoomDescription(description); + + // then + assertThat(PracticeRoomDescription).isNotNull(); + } + + @ParameterizedTest + @ValueSource(strings = { + "Practice rooms are equipped with soundproofing to ensure musicians can practice without disturbing others.https://www.musiciansfriend.com/keyboards-midi/pianos", + "In a practice room, musicians can hone their skills and perfect their technique in a private setting.http://www.practice.com/", + "Practice rooms often feature mirrors to allow musicians to observe their posture and technique while playing.: www.practice.com/Acoustic-Guitars.gc", + "Practice rooms may contain specialized equipment such as metronomes and music stands to aid musicians in their practice sessions.: https://musiciansfriend.com/", + "In a practice room, musicians can experiment with different instruments and musical styles to broaden their repertoire.http://practice.com/" + }) + void 합주실_설명에_링크가_포함되었다면_예외가_발생한다(String description) { + // given + + // when + Throwable ex = catchThrowable(() -> new PracticeRoomDescription(description)); + + // then + assertThat(ex).isInstanceOf(UnexpectedLinkInPracticeRoomDescriptionException.class); + } +} diff --git a/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomCommandServiceTest.java b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomCommandServiceTest.java new file mode 100644 index 0000000..8277d32 --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomCommandServiceTest.java @@ -0,0 +1,209 @@ +package com.ajou.hertz.unit.domain.practice_room.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.ajou.hertz.common.dto.request.CoordinateRequest; +import com.ajou.hertz.common.dto.request.FullAddressRequest; +import com.ajou.hertz.common.entity.Coordinate; +import com.ajou.hertz.common.entity.FullAddress; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import com.ajou.hertz.domain.practice_room.dto.request.CreateNewPracticeRoomRequest; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomHashtag; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomImage; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomRepository; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomCommandService; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomHashtagCommandService; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomImageCommandService; +import com.ajou.hertz.domain.user.constant.Gender; +import com.ajou.hertz.domain.user.constant.RoleType; +import com.ajou.hertz.domain.user.entity.User; +import com.ajou.hertz.domain.user.service.UserQueryService; +import com.ajou.hertz.util.ReflectionUtils; + +@DisplayName("[Unit] Service(Command) - practiceRoom") +@ExtendWith(MockitoExtension.class) +public class PracticeRoomCommandServiceTest { + + @InjectMocks + private PracticeRoomCommandService sut; + + @Mock + private UserQueryService userQueryService; + + @Mock + private PracticeRoomImageCommandService practiceRoomImageCommandService; + + @Mock + private PracticeRoomHashtagCommandService practiceRoomHashtagCommandService; + + @Mock + private PracticeRoomRepository practiceRoomRepository; + + @Test + void 새로_등록할_합주실의_정보가_주어지면_합주실_매물을_등록한다() throws Exception { + // given + long sellerId = 1L; + CreateNewPracticeRoomRequest practiceRoomRequest = createPracticeRoomRequest(); + User seller = createUser(1L); + PracticeRoom practiceRoom = createPracticeRoom(2L, seller); + List practiceRoomImages = List.of(createPracticeRoomImage(3L, practiceRoom)); + List practiceRoomHashtags = List.of(createPracticeRoomHashtag(4L, practiceRoom)); + given(userQueryService.getById(sellerId)).willReturn(seller); + given(practiceRoomRepository.save(any(PracticeRoom.class))).willReturn(practiceRoom); + given(practiceRoomImageCommandService.saveImages(eq(practiceRoom), ArgumentMatchers.>any())) + .willReturn(practiceRoomImages); + given(practiceRoomHashtagCommandService.saveHashtags(practiceRoom, practiceRoomRequest.getHashtags())) + .willReturn(practiceRoomHashtags); + + // when + PracticeRoomDto result = sut.createNewPracticeRoom(sellerId, practiceRoomRequest); + + // then + then(userQueryService).should().getById(sellerId); + then(practiceRoomRepository).should().save(any(PracticeRoom.class)); + then(practiceRoomImageCommandService) + .should() + .saveImages(eq(practiceRoom), ArgumentMatchers.>any()); + then(practiceRoomHashtagCommandService).should() + .saveHashtags(practiceRoom, practiceRoomRequest.getHashtags()); + verifyEveryMocksShouldHaveNoMoreInteractions(); + assertThat(result) + .hasFieldOrPropertyWithValue("id", practiceRoom.getId()) + .hasFieldOrPropertyWithValue("seller.id", seller.getId()); + assertThat(result.getImages()).hasSize(practiceRoomImages.size()); + assertThat(result.getHashtags()).hasSize(practiceRoomHashtags.size()); + } + + private void verifyEveryMocksShouldHaveNoMoreInteractions() { + then(userQueryService).shouldHaveNoMoreInteractions(); + then(practiceRoomRepository).shouldHaveNoMoreInteractions(); + then(practiceRoomImageCommandService).shouldHaveNoMoreInteractions(); + then(practiceRoomHashtagCommandService).shouldHaveNoMoreInteractions(); + } + + private MockMultipartFile createMultipartFile() { + return new MockMultipartFile( + "image", + "originalFilename", + MediaType.IMAGE_PNG_VALUE, + "content".getBytes() + ); + } + + private User createUser(long id) throws Exception { + return ReflectionUtils.createUser( + id, + Set.of(RoleType.USER), + "test@mail.com", + "password", + "12345", + "https://user-default-profile-image-url", + LocalDate.of(2024, 1, 1), + Gender.ETC, + "01012345678", + null + ); + } + + private FullAddress createFullAddress() { + return new FullAddress( + "서울특별시", + "강남구", + "청담동 123-456", + "null", + "아주빌딩 2층" + ); + } + + private Coordinate createCoordinate() { + return new Coordinate( + "37.123456", + "127.123456" + ); + } + + private PracticeRoomImage createPracticeRoomImage(Long id, PracticeRoom practiceRoom) throws Exception { + return ReflectionUtils.createPracticeRoomImage( + id, + practiceRoom, + "original-name", + "stored-name", + "https://instrument-image-url" + ); + } + + private PracticeRoomHashtag createPracticeRoomHashtag(Long id, PracticeRoom practiceRoom) throws Exception { + return ReflectionUtils.createPracticeRoomHashtag(id, practiceRoom, "content"); + } + + private PracticeRoom createPracticeRoom(long id, User seller) throws Exception { + return ReflectionUtils.createPracticeRoom( + id, + seller, + "Test electric guitar", + createFullAddress(), + createCoordinate(), + true, + true, + 10, + 100, + 1000, + (short)3, + "100", + true, + "description" + ); + } + + private FullAddressRequest createFullAddressRequest() throws Exception { + return ReflectionUtils.createFullAddressRequest( + "서울특별시 강남구 청담동 123-456", + "아주빌딩 2층" + ); + } + + private CoordinateRequest createCoordinateRequest() throws Exception { + return ReflectionUtils.createCoordinateRequest( + "37.123456", + "127.123456" + ); + } + + private CreateNewPracticeRoomRequest createPracticeRoomRequest() throws Exception { + return ReflectionUtils.createNewPracticeRoomRequest( + "Title", + createFullAddressRequest(), + createCoordinateRequest(), + true, + true, + 100, + 1000, + 10000, + (short)3, + "100", + true, + "description", + List.of(createMultipartFile()), + List.of("Band", "instrument") + ); + } + +} diff --git a/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomHashtagCommandServiceTest.java b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomHashtagCommandServiceTest.java new file mode 100644 index 0000000..567c404 --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomHashtagCommandServiceTest.java @@ -0,0 +1,121 @@ +package com.ajou.hertz.unit.domain.practice_room.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.ajou.hertz.common.entity.Coordinate; +import com.ajou.hertz.common.entity.FullAddress; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomHashtag; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomHashtagRepository; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomHashtagCommandService; +import com.ajou.hertz.domain.user.constant.Gender; +import com.ajou.hertz.domain.user.constant.RoleType; +import com.ajou.hertz.domain.user.entity.User; +import com.ajou.hertz.util.ReflectionUtils; + +@DisplayName("[Unit] Service(Command) - PracticeRoom Hashtag") +@ExtendWith(MockitoExtension.class) +public class PracticeRoomHashtagCommandServiceTest { + + @InjectMocks + private PracticeRoomHashtagCommandService sut; + + @Mock + private PracticeRoomHashtagRepository practiceRoomHashtagRepository; + + @Test + void 주어진_해시태그_내용들로_해시태그_entity를_생성_및_저장한다() throws Exception { + // given + PracticeRoom practiceRoom = createPracticeRoom(1L, createUser(2L)); + List hashtagsContentList = List.of("test1", "test2"); + List expectedResult = List.of( + createPracticeRoomHashtag(3L, practiceRoom, hashtagsContentList.get(0)), + createPracticeRoomHashtag(4L, practiceRoom, hashtagsContentList.get(1)) + ); + given(practiceRoomHashtagRepository.saveAll(ArgumentMatchers.>any())) + .willReturn(expectedResult); + + // when + List actualResult = sut.saveHashtags(practiceRoom, hashtagsContentList); + + // then + then(practiceRoomHashtagRepository).should().saveAll(ArgumentMatchers.>any()); + verifyEveryMocksShouldHaveNoMoreInteractions(); + assertThat(actualResult).hasSize(expectedResult.size()); + assertThat(actualResult.get(0).getId()).isEqualTo(expectedResult.get(0).getId()); + assertThat(actualResult.get(1).getId()).isEqualTo(expectedResult.get(1).getId()); + } + + private void verifyEveryMocksShouldHaveNoMoreInteractions() { + then(practiceRoomHashtagRepository).shouldHaveNoMoreInteractions(); + } + + private User createUser(long id) throws Exception { + return ReflectionUtils.createUser( + id, + Set.of(RoleType.USER), + "test@mail.com", + "password", + "12345", + "https://user-default-profile-image-url", + LocalDate.of(2024, 1, 1), + Gender.ETC, + "01012345678", + null + ); + } + + private FullAddress createFullAddress() { + return new FullAddress( + "서울특별시", + "강남구", + "청담동 123-456", + "null", + "아주빌딩 2층" + ); + } + + private Coordinate createCoordinate() { + return new Coordinate( + "37.123456", + "127.123456" + ); + } + + private PracticeRoom createPracticeRoom(long id, User seller) throws Exception { + return ReflectionUtils.createPracticeRoom( + id, + seller, + "Test electric guitar", + createFullAddress(), + createCoordinate(), + true, + true, + 100, + 1000, + 10000, + (short)3, + "100", + true, + "description" + ); + } + + private PracticeRoomHashtag createPracticeRoomHashtag(Long id, PracticeRoom PracticeRoom, String content) throws + Exception { + return ReflectionUtils.createPracticeRoomHashtag(id, PracticeRoom, content); + } +} diff --git a/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomImageCommandServiceTest.java b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomImageCommandServiceTest.java new file mode 100644 index 0000000..78a4d8e --- /dev/null +++ b/src/test/java/com/ajou/hertz/unit/domain/practice_room/service/PracticeRoomImageCommandServiceTest.java @@ -0,0 +1,157 @@ +package com.ajou.hertz.unit.domain.practice_room.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.BDDMockito.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.ajou.hertz.common.entity.Coordinate; +import com.ajou.hertz.common.entity.FullAddress; +import com.ajou.hertz.common.file.dto.FileDto; +import com.ajou.hertz.common.file.service.FileService; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomImage; +import com.ajou.hertz.domain.practice_room.repository.PracticeRoomImageRepository; +import com.ajou.hertz.domain.practice_room.service.PracticeRoomImageCommandService; +import com.ajou.hertz.domain.user.constant.Gender; +import com.ajou.hertz.domain.user.constant.RoleType; +import com.ajou.hertz.domain.user.entity.User; +import com.ajou.hertz.util.ReflectionUtils; + +@DisplayName("[Unit] Service(Command) - PracticeRoom Image") +@ExtendWith(MockitoExtension.class) +public class PracticeRoomImageCommandServiceTest { + + @InjectMocks + private PracticeRoomImageCommandService sut; + + @Mock + private FileService fileService; + + @Mock + private PracticeRoomImageRepository practiceRoomImageRepository; + + @Test + void 악기와_이미지_파일들이_주어지고_이미지_파일들을_업로드_한_후_주어진_악기에_대한_이미지_entities를_생성한다() throws Exception { + // given + List images = List.of( + createMultipartFile(), + createMultipartFile(), + createMultipartFile(), + createMultipartFile() + ); + PracticeRoom practiceRoom = createPracticeRoom(1L, createUser(2L)); + List expectedResult = List.of( + createPracticeRoomImage(3L, practiceRoom), + createPracticeRoomImage(4L, practiceRoom), + createPracticeRoomImage(5L, practiceRoom), + createPracticeRoomImage(6L, practiceRoom) + ); + given(fileService.uploadFiles(ArgumentMatchers.>any(), anyString())) + .willReturn(List.of(createFileDto(), createFileDto(), createFileDto(), createFileDto())); + given(practiceRoomImageRepository.saveAll(ArgumentMatchers.>any())) + .willReturn(expectedResult); + + // when + List actualResult = sut.saveImages(practiceRoom, images); + + // then + then(fileService).should().uploadFiles(ArgumentMatchers.>any(), anyString()); + then(practiceRoomImageRepository).should().saveAll(ArgumentMatchers.>any()); + verifyEveryMocksShouldHaveNoMoreInteractions(); + assertThat(actualResult.size()).isEqualTo(expectedResult.size()); + } + + private void verifyEveryMocksShouldHaveNoMoreInteractions() { + then(fileService).shouldHaveNoMoreInteractions(); + then(practiceRoomImageRepository).shouldHaveNoMoreInteractions(); + } + + private MockMultipartFile createMultipartFile() { + return new MockMultipartFile( + "image", + "originalFilename", + MediaType.IMAGE_PNG_VALUE, + "content".getBytes() + ); + } + + private User createUser(long id) throws Exception { + return ReflectionUtils.createUser( + id, + Set.of(RoleType.USER), + "test@mail.com", + "password", + "12345", + "https://user-default-profile-image-url", + LocalDate.of(2024, 1, 1), + Gender.ETC, + "01012345678", + null + ); + } + + private FullAddress createFullAddress() { + return new FullAddress( + "서울특별시", + "강남구", + "청담동 123-456", + "null", + "아주빌딩 2층" + ); + } + + private Coordinate createCoordinate() { + return new Coordinate( + "37.123456", + "127.123456" + ); + } + + private PracticeRoom createPracticeRoom(long id, User seller) throws Exception { + return ReflectionUtils.createPracticeRoom( + id, + seller, + "Test PracticeRoom", + createFullAddress(), + createCoordinate(), + true, + true, + 100, + 1000, + 10000, + (short)3, + "100", + true, + "description" + ); + } + + private PracticeRoomImage createPracticeRoomImage(long id, PracticeRoom practiceRoom) throws Exception { + return ReflectionUtils.createPracticeRoomImage( + id, + practiceRoom, + "original-image-name.png", + "stored-image-name", + "https://practiceRoom-image" + ); + } + + private FileDto createFileDto() throws Exception { + return ReflectionUtils.createFileDto("original-name", "stored-name", "https://file-url"); + } +} diff --git a/src/test/java/com/ajou/hertz/util/ReflectionUtils.java b/src/test/java/com/ajou/hertz/util/ReflectionUtils.java index dc2c781..ef3fdeb 100644 --- a/src/test/java/com/ajou/hertz/util/ReflectionUtils.java +++ b/src/test/java/com/ajou/hertz/util/ReflectionUtils.java @@ -14,8 +14,14 @@ import com.ajou.hertz.common.auth.dto.request.SendUserAuthCodeRequest; import com.ajou.hertz.common.auth.dto.request.VerifyUserAuthCodeRequest; import com.ajou.hertz.common.dto.AddressDto; +import com.ajou.hertz.common.dto.CoordinateDto; +import com.ajou.hertz.common.dto.FullAddressDto; import com.ajou.hertz.common.dto.request.AddressRequest; +import com.ajou.hertz.common.dto.request.CoordinateRequest; +import com.ajou.hertz.common.dto.request.FullAddressRequest; import com.ajou.hertz.common.entity.Address; +import com.ajou.hertz.common.entity.Coordinate; +import com.ajou.hertz.common.entity.FullAddress; import com.ajou.hertz.common.file.dto.FileDto; import com.ajou.hertz.common.kakao.dto.response.KakaoTokenResponse; import com.ajou.hertz.domain.administrative_area.entity.AdministrativeAreaEmd; @@ -68,6 +74,12 @@ import com.ajou.hertz.domain.instrument.entity.Instrument; import com.ajou.hertz.domain.instrument.entity.InstrumentHashtag; import com.ajou.hertz.domain.instrument.entity.InstrumentImage; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomDto; +import com.ajou.hertz.domain.practice_room.dto.PracticeRoomImageDto; +import com.ajou.hertz.domain.practice_room.dto.request.CreateNewPracticeRoomRequest; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoom; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomHashtag; +import com.ajou.hertz.domain.practice_room.entity.PracticeRoomImage; import com.ajou.hertz.domain.user.constant.Gender; import com.ajou.hertz.domain.user.constant.RoleType; import com.ajou.hertz.domain.user.dto.UserDto; @@ -308,6 +320,58 @@ public static AdministrativeAreaEmd createEmd(Long id, AdministrativeAreaSgg sgg return constructor.newInstance(id, sgg, name); } + public static PracticeRoom createPracticeRoom( + Long id, + User seller, + String title, + FullAddress tradeAddress, + Coordinate coordinate, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerDay, + Integer pricePerHour, + Integer pricePerMonth, + Short capacity, + String size, + Boolean hasParkingLot, + String description + ) throws Exception { + Constructor constructor = PracticeRoom.class.getDeclaredConstructor( + Long.class, User.class, String.class, FullAddress.class, Coordinate.class, Boolean.class, Boolean.class, + Integer.class, Integer.class, Integer.class, Short.class, String.class, Boolean.class, String.class); + constructor.setAccessible(true); + return constructor.newInstance( + id, seller, title, tradeAddress, coordinate, hasSoundEquipment, hasInstrument, pricePerDay, pricePerHour, + pricePerMonth, capacity, size, hasParkingLot, description + ); + } + + public static PracticeRoomHashtag createPracticeRoomHashtag( + Long id, + PracticeRoom practiceRoom, + String content + ) throws Exception { + Constructor constructor = PracticeRoomHashtag.class.getDeclaredConstructor( + Long.class, PracticeRoom.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(id, practiceRoom, content); + } + + public static PracticeRoomImage createPracticeRoomImage( + Long id, + PracticeRoom practiceRoom, + String originalName, + String storedName, + String url + ) throws Exception { + Constructor constructor = PracticeRoomImage.class.getDeclaredConstructor( + Long.class, PracticeRoom.class, String.class, String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(id, practiceRoom, originalName, storedName, url); + } + /** * DTO */ @@ -529,6 +593,62 @@ public static AudioEquipmentDto createAudioEquipmentDto( ); } + public static PracticeRoomImageDto createPracticeRoomImageDto(Long id, String name, String url) throws Exception { + Constructor constructor = PracticeRoomImageDto.class.getDeclaredConstructor( + Long.class, String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(id, name, url); + } + + public static FullAddressDto createFullAddressDto(String sido, String sgg, String lotNumberAddress, + String roadAddress, String detailAddress) throws Exception { + Constructor constructor = FullAddressDto.class.getDeclaredConstructor( + String.class, String.class, String.class, String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(sido, sgg, lotNumberAddress, roadAddress, detailAddress); + } + + public static CoordinateDto createCoordinateDto(String lat, String lng) throws Exception { + Constructor constructor = CoordinateDto.class.getDeclaredConstructor( + String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(lat, lng); + } + + public static PracticeRoomDto createPracticeRoomDto( + Long id, + UserDto seller, + String title, + FullAddressDto tradeAddress, + CoordinateDto coordinate, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerDay, + Integer pricePerHour, + Integer pricePerMonth, + Short capacity, + String size, + Boolean hasParkingLot, + String description, + List images, + List hashtags + ) throws Exception { + Constructor constructor = PracticeRoomDto.class.getDeclaredConstructor( + Long.class, UserDto.class, String.class, FullAddressDto.class, CoordinateDto.class, Boolean.class, + Boolean.class, Integer.class, Integer.class, Integer.class, Short.class, String.class, Boolean.class, + String.class, List.class, List.class + ); + constructor.setAccessible(true); + return constructor.newInstance( + id, seller, title, tradeAddress, coordinate, hasSoundEquipment, hasInstrument, + pricePerDay, pricePerHour, pricePerMonth, capacity, size, hasParkingLot, description, + images, hashtags + ); + } + /** * DTO(Request) */ @@ -900,6 +1020,52 @@ public static VerifyUserAuthCodeRequest createVerifyUserAuthCodeRequest( return constructor.newInstance(phoneNumber, code); } + public static CreateNewPracticeRoomRequest createNewPracticeRoomRequest( + String title, + FullAddressRequest tradeAddress, + CoordinateRequest coordinate, + Boolean hasSoundEquipment, + Boolean hasInstrument, + Integer pricePerDay, + Integer pricePerHour, + Integer pricePerMonth, + Short capacity, + String size, + Boolean hasParkingLot, + String description, + List images, + List hashtags + ) throws Exception { + Constructor constructor = + CreateNewPracticeRoomRequest.class.getDeclaredConstructor( + String.class, FullAddressRequest.class, CoordinateRequest.class, Boolean.class, Boolean.class, + Integer.class, Integer.class, Integer.class, Short.class, String.class, Boolean.class, String.class, + List.class, List.class + ); + constructor.setAccessible(true); + return constructor.newInstance( + title, tradeAddress, coordinate, hasSoundEquipment, hasInstrument, pricePerDay, pricePerHour, pricePerMonth, + capacity, size, hasParkingLot, description, images, hashtags + ); + } + + public static FullAddressRequest createFullAddressRequest(String fullAddress, String detailAddress) throws + Exception { + Constructor constructor = FullAddressRequest.class.getDeclaredConstructor( + String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(fullAddress, detailAddress); + } + + public static CoordinateRequest createCoordinateRequest(String lat, String lng) throws Exception { + Constructor constructor = CoordinateRequest.class.getDeclaredConstructor( + String.class, String.class + ); + constructor.setAccessible(true); + return constructor.newInstance(lat, lng); + } + /** * DTO(Response) */