From 2cbac84fe491f662e5793ceea15e4951d76a0424 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Wed, 20 Sep 2023 10:16:56 +0900 Subject: [PATCH 1/8] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=EC=9D=98=20?= =?UTF-8?q?=EA=B4=80=EC=8B=AC=20=ED=83=9C=EA=B7=B8=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=8B=9C=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20N+1=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #564 --- .../member/domain/InterestTagRepository.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/member/domain/InterestTagRepository.java b/backend/emm-sale/src/main/java/com/emmsale/member/domain/InterestTagRepository.java index 0ce44b05a..71e5ee471 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/member/domain/InterestTagRepository.java +++ b/backend/emm-sale/src/main/java/com/emmsale/member/domain/InterestTagRepository.java @@ -6,20 +6,23 @@ import org.springframework.data.repository.query.Param; public interface InterestTagRepository extends JpaRepository { - - List findInterestTagsByMemberId(final Long memberId); - + + @Query("select it from InterestTag it " + + "join fetch it.tag " + + "where it.member.id = :memberId") + List findInterestTagsByMemberId(@Param("memberId") final Long memberId); + boolean existsByTagIdIn(List tagIds); - + @Query("select it from InterestTag it " + "where it.member = :member " + "and it.tag.id in :deleteTagId") List findAllByMemberAndTagIds( @Param("member") final Member member, @Param("deleteTagId") final List deleteTagId); - + @Query("select it from InterestTag it join fetch it.tag where it.tag.id in :ids") List findInterestTagsByTagIdIn(@Param("ids") final List ids); - + void deleteAllByMember(Member member); } From 53d2ab9e6345e3b386bcf78701beeec15a37d6f0 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Sat, 23 Sep 2023 13:27:18 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1/=EC=88=98=EC=A0=95=20API=EC=9D=98=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20=EB=AA=85=EC=84=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #633 --- .../src/documentTest/asciidoc/index.adoc | 9 +- .../java/com/emmsale/EventApiTest.java | 484 +++++++++++------- .../java/com/emmsale/event/api/EventApi.java | 28 +- .../event/application/EventService.java | 74 +-- .../application/dto/EventDetailRequest.java | 21 +- .../event/application/EventServiceTest.java | 254 +++++---- 6 files changed, 480 insertions(+), 390 deletions(-) diff --git a/backend/emm-sale/src/documentTest/asciidoc/index.adoc b/backend/emm-sale/src/documentTest/asciidoc/index.adoc index acf07b463..c78058d74 100644 --- a/backend/emm-sale/src/documentTest/asciidoc/index.adoc +++ b/backend/emm-sale/src/documentTest/asciidoc/index.adoc @@ -277,13 +277,10 @@ include::{snippets}/find-recruitment-post/response-fields.adoc[] POST -[source] ----- -/events?name=인프콘 2023&location=코엑스&informationUrl=https://~~~&startDateTime=2023:06:01:12:00:00&endDateTime=2023:09:01:12:00:00&applyStartDateTime=2023:05:01:12:00:00&applyEndDateTime=2023:06:01:12:00:00&tags=백엔드,안드로이드&imageUrl=https://image.url&type=CONFERENCE&eventMode=ON_OFFLINE&paymentType=FREE ----- +.HTTP request +include::{snippets}/add-event/http-request.adoc[] .HTTP request 설명 -include::{snippets}/add-event/request-parameters.adoc[] include::{snippets}/add-event/request-parts.adoc[] .HTTP response @@ -298,7 +295,7 @@ include::{snippets}/add-event/response-fields.adoc[] include::{snippets}/update-event/http-request.adoc[] .HTTP request 설명 -include::{snippets}/update-event/request-fields.adoc[] +include::{snippets}/update-event/request-parts.adoc[] .HTTP response include::{snippets}/update-event/http-response.adoc[] diff --git a/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java b/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java index 03789ff73..615021377 100644 --- a/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java +++ b/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java @@ -1,17 +1,14 @@ package com.emmsale; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.partWithName; import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.restdocs.request.RequestDocumentation.requestParts; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.emmsale.event.EventFixture; @@ -26,11 +23,12 @@ import com.emmsale.event.domain.PaymentType; import com.emmsale.tag.TagFixture; import com.emmsale.tag.application.dto.TagRequest; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; @@ -43,12 +41,12 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.mock.web.MockMultipartFile; import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.restdocs.payload.PayloadDocumentation; -import org.springframework.restdocs.payload.RequestFieldsSnippet; import org.springframework.restdocs.payload.ResponseFieldsSnippet; import org.springframework.restdocs.request.RequestDocumentation; import org.springframework.restdocs.request.RequestParametersSnippet; @@ -59,7 +57,7 @@ @WebMvcTest(EventApi.class) class EventApiTest extends MockMvcTestHelper { - + private static final ResponseFieldsSnippet EVENT_DETAIL_RESPONSE_FILED = PayloadDocumentation.responseFields( fieldWithPath("id").type(JsonFieldType.NUMBER).description("event 식별자"), fieldWithPath("name").type(JsonFieldType.STRING) @@ -89,7 +87,7 @@ class EventApiTest extends MockMvcTestHelper { fieldWithPath("imageUrls[]").description("이미지 URL들").optional(), fieldWithPath("organization").description("행사기관") ); - + @Test @DisplayName("컨퍼런스의 상세정보를 조회할 수 있다.") void findEvent() throws Exception { @@ -103,16 +101,16 @@ void findEvent() throws Exception { "ENDED", List.of("코틀린", "백엔드", "안드로이드"), "https://www.image.com", 2, -12, EventType.COMPETITION.toString(), List.of("imageUrl1", "imageUrl2"), "인프런"); - + Mockito.when(eventService.findEvent(ArgumentMatchers.anyLong(), any())) .thenReturn(eventDetailResponse); - + //when mockMvc.perform(get("/events/" + eventId)).andExpect( status().isOk()) .andDo(MockMvcRestDocumentation.document("find-event", EVENT_DETAIL_RESPONSE_FILED)); } - + @Test @DisplayName("특정 카테고리의 행사 목록을 조회할 수 있으면 200 OK를 반환한다.") void findEvents() throws Exception { @@ -130,7 +128,7 @@ void findEvents() throws Exception { .description("필터링하려는 상태(UPCOMING, IN_PROGRESS, ENDED)(option)") .optional() ); - + final ResponseFieldsSnippet responseFields = PayloadDocumentation.responseFields( fieldWithPath("[].id").type(JsonFieldType.NUMBER).description("행사 id"), fieldWithPath("[].name").type(JsonFieldType.STRING).description("행사명"), @@ -155,7 +153,7 @@ void findEvents() throws Exception { fieldWithPath("[].paymentType").type(JsonFieldType.STRING) .description("행사 유료 여부(유료, 무료, 유무료)") ); - + final List eventResponses = List.of( new EventResponse(1L, "인프콘 2023", LocalDateTime.parse("2023-06-03T12:00:00"), LocalDateTime.parse("2023-09-03T12:00:00"), @@ -173,12 +171,12 @@ void findEvents() throws Exception { "https://biz.pusan.ac.kr/dext5editordata/2022/08/20220810_160546511_10103.jpg", 3, -18, EventMode.ONLINE.getValue(), PaymentType.PAID.getValue()) ); - + Mockito.when(eventService.findEvents(any(EventType.class), - any(LocalDate.class), ArgumentMatchers.eq("2023-07-01"), - ArgumentMatchers.eq("2023-07-31"), - ArgumentMatchers.eq(null), any())).thenReturn(eventResponses); - + any(LocalDate.class), eq("2023-07-01"), + eq("2023-07-31"), + eq(null), any())).thenReturn(eventResponses); + // when & then mockMvc.perform(get("/events") .param("category", "CONFERENCE") @@ -189,80 +187,108 @@ void findEvents() throws Exception { .andExpect(status().isOk()) .andDo(MockMvcRestDocumentation.document("find-events", requestParameters, responseFields)); } - + @Test @DisplayName("이벤트를 성공적으로 업데이트하면 200, OK를 반환한다.") void updateEventTest() throws Exception { //given + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); final long eventId = 1L; final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - - final EventDetailRequest request = new EventDetailRequest(event.getName(), event.getLocation(), - event.getInformationUrl(), event.getEventPeriod().getStartDate(), + + final EventDetailRequest request = new EventDetailRequest(event.getName(), + event.getLocation(), event.getInformationUrl(), event.getEventPeriod().getStartDate(), event.getEventPeriod().getEndDate(), - event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), tags, - event.getImageUrl(), event.getType(), EventMode.OFFLINE, PaymentType.PAID, null, "행사기관"); - - final EventDetailResponse response = new EventDetailResponse(eventId, request.getName(), + event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), + tags, event.getImageUrl(), event.getType(), EventMode.ON_OFFLINE, PaymentType.FREE, + "행사기관"); + + final EventDetailResponse response = new EventDetailResponse(1L, request.getName(), request.getInformationUrl(), request.getStartDateTime(), request.getEndDateTime(), request.getApplyStartDateTime(), request.getApplyEndDateTime(), request.getLocation(), EventStatus.IN_PROGRESS.name(), EventStatus.ENDED.name(), - tags.stream().map(TagRequest::getName).collect(Collectors.toList()), request.getImageUrl(), - 10, 10, request.getType().toString(), Collections.emptyList(), "행사기관"); - - Mockito.when(eventService.updateEvent(any(), any(), - any())).thenReturn(response); - - final RequestFieldsSnippet requestFieldsSnippet = requestFields( - fieldWithPath("name").description("행사(Event) 이름"), - fieldWithPath("location").description("행사(Event) 장소"), - fieldWithPath("startDateTime").description("행사(Event) 시작일시"), - fieldWithPath("endDateTime").description("행사(Event) 종료일시"), - fieldWithPath("applyStartDateTime").description("행사(Event) 신청시작일시"), - fieldWithPath("applyEndDateTime").description("행사(Event) 신청종료일시"), - fieldWithPath("informationUrl").description("행사(Event) 상세 정보 URL"), - fieldWithPath("tags[].name").description("연관 태그명"), - fieldWithPath("imageUrl").description("행사(Event) 이미지url"), - fieldWithPath("type").description("행사(Event) 타입"), - fieldWithPath("eventMode").description("행사 온오프라인 여부(ON_OFFLINE, OFFLINE, ONLINE)"), - fieldWithPath("paymentType").description("행사 유료 여부(PAID, FREE, FREE_PAID)"), - fieldWithPath("images").description("이미지들").optional(), - fieldWithPath("organization").description("행사 기관") + tags.stream().map(TagRequest::getName).collect(Collectors.toList()), + request.getImageUrl(), 10, 10, request.getType().toString(), + List.of("imageUrl1", "imageUrl2"), "행사기관"); + + Mockito.when(eventService.updateEvent(eq(eventId), any(EventDetailRequest.class), any(), any())) + .thenReturn(response); + + String contents = objectMapper.writeValueAsString(request); + + final RequestPartsSnippet requestPartsSnippet = requestParts( + partWithName("images").description("이미지들").optional(), + partWithName("request").description("행사 정보들"), + partWithName("request.name").description("행사(Event) 이름").optional(), + partWithName("request.location").description("행사(Event) 장소").optional(), + partWithName("request.startDateTime").description("행사(Event) 시작일시").optional(), + partWithName("request.endDateTime").description("행사(Event) 종료일시").optional(), + partWithName("request.applyStartDateTime").description("행사(Event) 신청시작일시").optional(), + partWithName("request.applyEndDateTime").description("행사(Event) 신청종료일시").optional(), + partWithName("request.informationUrl").description("행사(Event) 상세 정보 URL").optional(), + partWithName("request.tags[]").description("연관 태그명").optional(), + partWithName("request.imageUrl").description("행사(Event) imageUrl").optional(), + partWithName("request.type").description("Event 타입").optional(), + partWithName("request.eventMode").description("행사 온오프라인 여부(ON_OFFLINE, OFFLINE, ONLINE)") + .optional(), + partWithName("request.paymentType").description("행사 유료 여부(PAID, FREE, FREE_PAID)") + .optional(), + partWithName("request.organization").description("행사 주최 기관").optional() ); - + + //when + MockMultipartHttpServletRequestBuilder builder = multipart(HttpMethod.PUT, "/events/" + eventId) + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))); + + final ResultActions result = mockMvc.perform(builder); + //when & then - mockMvc.perform(put("/events/" + eventId) - .contentType(MediaType.APPLICATION_JSON_VALUE) - .content(objectMapper.writeValueAsString(request))) - .andExpect(status().isOk()) + result.andExpect(status().isOk()) .andDo(MockMvcResultHandlers.print()) - .andDo(MockMvcRestDocumentation.document("update-event", requestFieldsSnippet, + .andDo(MockMvcRestDocumentation.document("update-event", requestPartsSnippet, EVENT_DETAIL_RESPONSE_FILED)); + + } - + @Test @DisplayName("이벤트를 성공적으로 삭제하면 204, NO_CONTENT를 반환한다.") void deleteEventTest() throws Exception { //given final long eventId = 1L; - + Mockito.doNothing().when(eventService).deleteEvent(eventId); //when final ResultActions result = mockMvc.perform( delete("/events/" + eventId)); - + //then result.andExpect(status().isNoContent()) .andDo(MockMvcResultHandlers.print()).andDo( MockMvcRestDocumentation.document("delete-event")); } - + @Nested class AddEvent { - + @Test @DisplayName("이벤트를 성공적으로 추가하면 201, CREATED 를 반환한다.") void addEventTest() throws Exception { @@ -273,26 +299,26 @@ void addEventTest() throws Exception { MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - + final EventDetailRequest request = new EventDetailRequest(event.getName(), event.getLocation(), event.getInformationUrl(), event.getEventPeriod().getStartDate(), event.getEventPeriod().getEndDate(), event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), tags, event.getImageUrl(), event.getType(), EventMode.ON_OFFLINE, PaymentType.FREE, - List.of(image1, image2), "행사기관"); - + "행사기관"); + final EventDetailResponse response = new EventDetailResponse(1L, request.getName(), request.getInformationUrl(), request.getStartDateTime(), request.getEndDateTime(), request.getApplyStartDateTime(), request.getApplyEndDateTime(), @@ -300,127 +326,130 @@ void addEventTest() throws Exception { tags.stream().map(TagRequest::getName).collect(Collectors.toList()), request.getImageUrl(), 10, 10, request.getType().toString(), List.of("imageUrl1", "imageUrl2"), "행사기관"); - - Mockito.when(eventService.addEvent(any(), any())) + + Mockito.when(eventService.addEvent(any(EventDetailRequest.class), any(), any())) .thenReturn(response); - - final RequestParametersSnippet requestParam = requestParameters( - parameterWithName("name").description("행사(Event) 이름"), - parameterWithName("location").description("행사(Event) 장소"), - parameterWithName("startDateTime").description("행사(Event) 시작일시"), - parameterWithName("endDateTime").description("행사(Event) 종료일시"), - parameterWithName("applyStartDateTime").description("행사(Event) 신청시작일시"), - parameterWithName("applyEndDateTime").description("행사(Event) 신청종료일시"), - parameterWithName("informationUrl").description("행사(Event) 상세 정보 URL"), - parameterWithName("tags").description("연관 태그명"), - parameterWithName("imageUrl").description("행사(Event) imageUrl"), - parameterWithName("type").description("Event 타입"), - parameterWithName("eventMode").description("행사 온오프라인 여부(ON_OFFLINE, OFFLINE, ONLINE)"), - parameterWithName("paymentType").description("행사 유료 여부(PAID, FREE, FREE_PAID)") - ); - + + String contents = objectMapper.writeValueAsString(request); + final RequestPartsSnippet requestPartsSnippet = requestParts( - partWithName("images").description("이미지들").optional() + partWithName("images").description("이미지들").optional(), + partWithName("request").description("행사 정보들"), + partWithName("request.name").description("행사(Event) 이름").optional(), + partWithName("request.location").description("행사(Event) 장소").optional(), + partWithName("request.startDateTime").description("행사(Event) 시작일시").optional(), + partWithName("request.endDateTime").description("행사(Event) 종료일시").optional(), + partWithName("request.applyStartDateTime").description("행사(Event) 신청시작일시").optional(), + partWithName("request.applyEndDateTime").description("행사(Event) 신청종료일시").optional(), + partWithName("request.informationUrl").description("행사(Event) 상세 정보 URL").optional(), + partWithName("request.tags[]").description("연관 태그명").optional(), + partWithName("request.imageUrl").description("행사(Event) imageUrl").optional(), + partWithName("request.type").description("Event 타입").optional(), + partWithName("request.eventMode").description("행사 온오프라인 여부(ON_OFFLINE, OFFLINE, ONLINE)") + .optional(), + partWithName("request.paymentType").description("행사 유료 여부(PAID, FREE, FREE_PAID)") + .optional(), + partWithName("request.organization").description("행사 주최 기관").optional() ); - + //when MockMultipartHttpServletRequestBuilder builder = multipart("/events") .file("images", image1.getBytes()) - .file("images", image2.getBytes()); - - builder.param("name", request.getName()) - .param("location", request.getLocation()) - .param("informationUrl", request.getInformationUrl()) - .param("startDateTime", - request.getStartDateTime().format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("endDateTime", - request.getEndDateTime().format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyStartDateTime", request.getApplyStartDateTime() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyEndDateTime", request.getApplyEndDateTime() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("tags", request.getTags().stream().map(TagRequest::getName) - .collect(Collectors.joining(","))) - .param("imageUrl", request.getImageUrl()) - .param("type", request.getType().toString()) - .param("eventMode", request.getEventMode().toString()) - .param("paymentType", request.getPaymentType().toString()); - + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))); + final ResultActions result = mockMvc.perform(builder); - + //then result.andExpect(status().isCreated()) .andDo(MockMvcResultHandlers.print()) - .andDo(MockMvcRestDocumentation.document("add-event", requestParam, - EVENT_DETAIL_RESPONSE_FILED, requestPartsSnippet)); + .andDo(MockMvcRestDocumentation.document("add-event", EVENT_DETAIL_RESPONSE_FILED, + requestPartsSnippet)); } - + @ParameterizedTest @NullSource @EmptySource @DisplayName("이름에 빈 값이 들어올 경우 400 BAD_REQUEST를 반환한다.") void addEventWithEmptyNameTest(final String eventName) throws Exception { //given + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - + final EventDetailRequest request = new EventDetailRequest( + eventName, event.getLocation(), event.getInformationUrl(), event.getEventPeriod() + .getStartDate(), event.getEventPeriod().getEndDate(), + event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), + tags, event.getImageUrl(), event.getType(), event.getEventMode(), + event.getPaymentType(), event.getOrganization()); + String contents = objectMapper.writeValueAsString(request); //when & then - mockMvc.perform(post("/events") - .param("name", eventName) - .param("location", event.getLocation()) - .param("informationUrl", event.getInformationUrl()) - .param("startDateTime", event.getEventPeriod().getStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("endDateTime", event.getEventPeriod().getEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyStartDateTime", event.getEventPeriod().getApplyStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyEndDateTime", event.getEventPeriod().getApplyEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("imageUrl", event.getImageUrl()) - .param("type", event.getType().toString()) - .param("eventMode", EventMode.ON_OFFLINE.toString()) - .param("paymentType", PaymentType.FREE.toString()) - .param("tags", tags.stream().map(TagRequest::getName) - .collect(Collectors.joining(",")))) + mockMvc.perform(multipart("/events") + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))) + ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @NullSource @EmptySource @DisplayName("장소에 빈 값이 들어올 경우 400 BAD_REQUEST를 반환한다.") void addEventWithEmptyLocationTest(final String eventLocation) throws Exception { //given + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - + final EventDetailRequest request = new EventDetailRequest( + event.getName(), eventLocation, event.getInformationUrl(), event.getEventPeriod() + .getStartDate(), event.getEventPeriod().getEndDate(), + event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), + tags, event.getImageUrl(), event.getType(), event.getEventMode(), + event.getPaymentType(), event.getOrganization()); + String contents = objectMapper.writeValueAsString(request); //when & then - mockMvc.perform(post("/events") - .param("name", event.getName()) - .param("location", eventLocation) - .param("informationUrl", eventLocation) - .param("startDateTime", event.getEventPeriod().getStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("endDateTime", event.getEventPeriod().getEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyStartDateTime", event.getEventPeriod().getApplyStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyEndDateTime", event.getEventPeriod().getApplyEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("imageUrl", event.getImageUrl()) - .param("type", event.getType().toString()) - .param("eventMode", EventMode.ON_OFFLINE.toString()) - .param("paymentType", PaymentType.FREE.toString()) - .param("tags", tags.stream().map(TagRequest::getName) - .collect(Collectors.joining(",")))) + mockMvc.perform(multipart("/events") + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))) + ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @ValueSource(strings = {"httpexample.com", "http:example.com", "http:/example.com", "httpsexample.com", "https:example.com", "https:/example.com"}) @@ -428,52 +457,90 @@ void addEventWithEmptyLocationTest(final String eventLocation) throws Exception @DisplayName("상세 URL에 http:// 혹은 https://로 시작하지 않는 값이 들어올 경우 400 BAD_REQUEST를 반환한다.") void addEventWithInvalidInformationUrlTest(final String informationUrl) throws Exception { //given + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - - //when - mockMvc.perform(post("/events") - .param("name", event.getName()) - .param("location", event.getLocation()) - .param("informationUrl", informationUrl) - .param("startDateTime", event.getEventPeriod().getStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("endDateTime", event.getEventPeriod().getEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyStartDateTime", event.getEventPeriod().getApplyStartDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("applyEndDateTime", event.getEventPeriod().getApplyEndDate() - .format(DateTimeFormatter.ofPattern("yyyy:MM:dd:HH:mm:ss"))) - .param("imageUrl", event.getImageUrl()) - .param("type", event.getType().toString()) - .param("eventMode", EventMode.ON_OFFLINE.toString()) - .param("paymentType", PaymentType.FREE.toString()) - .param("tags", tags.stream().map(TagRequest::getName) - .collect(Collectors.joining(",")))) + final EventDetailRequest request = new EventDetailRequest( + event.getName(), event.getLocation(), informationUrl, event.getEventPeriod() + .getStartDate(), event.getEventPeriod().getEndDate(), + event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), + tags, event.getImageUrl(), event.getType(), event.getEventMode(), + event.getPaymentType(), event.getOrganization()); + String contents = objectMapper.writeValueAsString(request); + //when & then + mockMvc.perform(multipart("/events") + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))) + ) .andExpect(status().isBadRequest()); - } - + @ParameterizedTest @ValueSource(strings = {"23-01-01T12:00:00", "2023-1-01T12:00:00", "2023-01-1T12:00:00", "2023-01-01T2:00:00", "2023-01-01T12:0:00", "2023-01-01T12:00:0"}) @NullSource @DisplayName("시작 일시에 null 혹은 다른 형식의 일시 값이 들어올 경우 400 BAD_REQUEST를 반환한다.") - void addEventWithUnformattedStartDateTimeTest(final String startDateTime) throws Exception { + void addEventWithUnformattedStartDateTimeTest(final String startDateTime) + throws Exception { //when & then - mockMvc.perform(post("/events").contentType(MediaType.APPLICATION_JSON_VALUE) - .param("name", "인프콘 2023") - .param("location", "코엑스") - .param("informationUrl", "https://~~~") - .param("startDateTime", startDateTime) // 이 변수의 값을 확인하고 올바른 값을 설정하세요. - .param("endDateTime", "2023-01-02T12:00:00") - .param("tags[0].name", "백엔드") - .param("tags[1].name", "안드로이드")) + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final Event event = EventFixture.인프콘_2023(); + + Map request = new HashMap<>(); + request.put("name", event.getName()); + request.put("location", event.getLocation()); + request.put("informationUrl", event.getInformationUrl()); + request.put("startDateTime", startDateTime); + request.put("endDateTime", event.getEventPeriod().getEndDate().toString()); + request.put("applyStartDateTime", event.getEventPeriod().getApplyStartDate().toString()); + request.put("applyEndDateTime", event.getEventPeriod().getApplyEndDate().toString()); + request.put("imageUrl", event.getImageUrl()); + request.put("type", event.getType().name()); + request.put("eventMode", event.getEventMode().name()); + request.put("paymentType", event.getPaymentType().name()); + request.put("organization", event.getOrganization()); + + String contents = objectMapper.writeValueAsString(request); + //when & then + mockMvc.perform(multipart("/events") + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))) + ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @ValueSource(strings = {"23-01-02T12:00:00", "2023-1-02T12:00:00", "2023-01-2T12:00:00", "2023-01-02T2:00:00", "2023-01-02T12:0:00", "2023-01-02T12:00:0"}) @@ -481,16 +548,45 @@ void addEventWithUnformattedStartDateTimeTest(final String startDateTime) throws @DisplayName("종료 일시에 null 혹은 다른 형식의 일시 값이 들어올 경우 400 BAD_REQUEST를 반환한다.") void addEventWithUnformattedEndDateTimeTest(final String endDateTime) throws Exception { //when & then - mockMvc.perform(post("/events") - .param("name", "인프콘 2023") - .param("location", "코엑스") - .param("informationUrl", "https://~~~") - .param("startDateTime", "2023-01-01T12:00:00") - .param("endDateTime", endDateTime) // 이 변수의 값을 확인하고 올바른 값을 설정하세요. - .param("tags[0].name", "백엔드") - .param("tags[1].name", "안드로이드")) + final MockMultipartFile image1 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final MockMultipartFile image2 = new MockMultipartFile( + "picture", + "picture.jpg", + MediaType.TEXT_PLAIN_VALUE, + "test data".getBytes() + ); + + final Event event = EventFixture.인프콘_2023(); + + Map request = new HashMap<>(); + request.put("name", event.getName()); + request.put("location", event.getLocation()); + request.put("informationUrl", event.getInformationUrl()); + request.put("startDateTime", event.getEventPeriod().getStartDate().toString()); + request.put("endDateTime", endDateTime); + request.put("applyStartDateTime", event.getEventPeriod().getApplyStartDate().toString()); + request.put("applyEndDateTime", event.getEventPeriod().getApplyEndDate().toString()); + request.put("imageUrl", event.getImageUrl()); + request.put("type", event.getType().name()); + request.put("eventMode", event.getEventMode().name()); + request.put("paymentType", event.getPaymentType().name()); + request.put("organization", event.getOrganization()); + + String contents = objectMapper.writeValueAsString(request); + //when & then + mockMvc.perform(multipart("/events") + .file("images", image1.getBytes()) + .file("images", image2.getBytes()) + .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( + StandardCharsets.UTF_8))) + ) .andExpect(status().isBadRequest()); } } - } diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java index 41abaf787..9803f0394 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java @@ -11,30 +11,32 @@ import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/events") @RequiredArgsConstructor public class EventApi { - + private final EventService eventService; - + @GetMapping("/{id}") public ResponseEntity findEventById(@PathVariable final Long id) { return ResponseEntity.ok(eventService.findEvent(id, LocalDate.now())); } - + @GetMapping public ResponseEntity> findEvents( @RequestParam final EventType category, @@ -45,20 +47,22 @@ public ResponseEntity> findEvents( return ResponseEntity.ok( eventService.findEvents(category, LocalDate.now(), startDate, endDate, tags, statuses)); } - - @PostMapping + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.CREATED) - public EventDetailResponse addEvent(@Valid final EventDetailRequest request) { - return eventService.addEvent(request, LocalDate.now()); + public EventDetailResponse addEvent(@RequestPart @Valid final EventDetailRequest request, + @RequestPart List images) { + return eventService.addEvent(request, images, LocalDate.now()); } - + @PutMapping("/{eventId}") @ResponseStatus(HttpStatus.OK) public EventDetailResponse updateEvent(@PathVariable final Long eventId, - @RequestBody @Valid final EventDetailRequest request) { - return eventService.updateEvent(eventId, request, LocalDate.now()); + @RequestPart @Valid final EventDetailRequest request, + @RequestPart List images) { + return eventService.updateEvent(eventId, request, images, LocalDate.now()); } - + @DeleteMapping("/{eventId}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteEvent(@PathVariable final Long eventId) { diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java b/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java index 16af445b5..70b1eb213 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java @@ -38,48 +38,49 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; @Service @Transactional @RequiredArgsConstructor public class EventService { - + private static final String MIN_DATE = "2000-01-01"; private static final String MAX_DATE = "2999-12-31"; - + private final EventRepository eventRepository; private final EventTagRepository eventTagRepository; private final TagRepository tagRepository; private final EventPublisher eventPublisher; private final ImageCommandService imageCommandService; private final ImageRepository imageRepository; - + @Transactional(readOnly = true) public EventDetailResponse findEvent(final Long id, final LocalDate today) { final Event event = eventRepository.findById(id) .orElseThrow(() -> new EventException(NOT_FOUND_EVENT)); - + final List imageUrls = imageRepository .findAllByTypeAndContentId(ImageType.EVENT, event.getId()) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + return EventDetailResponse.from(event, today, imageUrls); } - + @Transactional(readOnly = true) public List findEvents(final EventType category, final LocalDate nowDate, final String startDate, final String endDate, final List tagNames, final List statuses) { Specification spec = Specification.where(filterByCategory(category)); - + if (isExistTagNames(tagNames)) { validateTags(tagNames); spec = spec.and(filterByTags(tagNames)); } - + if (isExistFilterDate(startDate, endDate)) { final LocalDateTime startDateTime = validateStartDate(startDate); final LocalDateTime endDateTime = validateEndDate(endDate); @@ -89,25 +90,25 @@ public List findEvents(final EventType category, final List events = eventRepository.findAll(spec); final EnumMap> eventsForEventStatus = groupByEventStatus(nowDate, events); - + return filterByStatuses(nowDate, statuses, eventsForEventStatus); } - + private boolean isExistTagNames(final List tagNames) { return tagNames != null; } - + private void validateTags(final List tagNames) { final List tags = tagRepository.findByNameIn(tagNames); if (tags.size() != tagNames.size()) { throw new TagException(NOT_FOUND_TAG); } } - + private boolean isExistFilterDate(final String startDate, final String endDate) { return startDate != null || endDate != null; } - + private LocalDateTime validateStartDate(final String date) { try { if (date == null) { @@ -118,7 +119,7 @@ private LocalDateTime validateStartDate(final String date) { throw new EventException(EventExceptionType.INVALID_DATE_FORMAT); } } - + private LocalDateTime validateEndDate(final String date) { try { if (date == null) { @@ -129,14 +130,14 @@ private LocalDateTime validateEndDate(final String date) { throw new EventException(EventExceptionType.INVALID_DATE_FORMAT); } } - + private void validateEndDateAfterDateStart(final LocalDateTime startDate, final LocalDateTime endDate) { if (endDate.isBefore(startDate)) { throw new EventException(EventExceptionType.START_DATE_AFTER_END_DATE); } } - + private EnumMap> groupByEventStatus(final LocalDate nowDate, final List events) { return events.stream() @@ -146,7 +147,7 @@ private EnumMap> groupByEventStatus(final LocalDate now () -> new EnumMap<>(EventStatus.class), toList()) ); } - + private List filterByStatuses( final LocalDate today, final List statuses, @@ -157,11 +158,11 @@ private List filterByStatuses( } return EventResponse.mergeEventResponses(today, eventsForEventStatus); } - + private boolean isExistStatusName(final List statuses) { return statuses != null; } - + private List filterEventResponseByStatuses( final LocalDate today, final List statuses, @@ -177,33 +178,34 @@ private List filterEventResponseByStatuses( return combinedEvents; }); } - - public EventDetailResponse addEvent(final EventDetailRequest request, final LocalDate today) { + + public EventDetailResponse addEvent(final EventDetailRequest request, + final List images, final LocalDate today) { final Event event = eventRepository.save(request.toEvent()); final List tags = findAllPersistTagsOrElseThrow(request.getTags()); event.addAllEventTags(tags); - + final List imageUrls = imageCommandService - .saveImages(ImageType.EVENT, event.getId(), request.getImages()) + .saveImages(ImageType.EVENT, event.getId(), images) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + eventPublisher.publish(event); - + return EventDetailResponse.from(event, today, imageUrls); } - + public EventDetailResponse updateEvent(final Long eventId, final EventDetailRequest request, - final LocalDate today) { + final List images, final LocalDate today) { final Event event = eventRepository.findById(eventId) .orElseThrow(() -> new EventException(NOT_FOUND_EVENT)); - + final List tags = findAllPersistTagsOrElseThrow(request.getTags()); - + eventTagRepository.deleteAllByEventId(eventId); - + final Event updatedEvent = event.updateEventContent( request.getName(), request.getLocation(), @@ -214,30 +216,30 @@ public EventDetailResponse updateEvent(final Long eventId, final EventDetailRequ request.getInformationUrl(), tags ); - + final List imageUrls = imageRepository .findAllByTypeAndContentId(ImageType.EVENT, event.getId()) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + return EventDetailResponse.from(updatedEvent, today, imageUrls); } - + public void deleteEvent(final Long eventId) { if (!eventRepository.existsById(eventId)) { throw new EventException(NOT_FOUND_EVENT); } - + eventRepository.deleteById(eventId); } - + private List findAllPersistTagsOrElseThrow(final List tags) { if (tags == null || tags.isEmpty()) { return new ArrayList<>(); } - + return tags.stream() .map(tag -> tagRepository.findByName(tag.getName()) .orElseThrow(() -> new EventException(EventExceptionType.NOT_FOUND_TAG))) diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java index 77e8e2819..29e4c20e8 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java @@ -14,15 +14,14 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor @Getter @Setter public class EventDetailRequest { - + private static final String DATE_TIME_FORMAT = "yyyy:MM:dd:HH:mm:ss"; - + @NotBlank(message = "행사의 이름을 입력해 주세요.") private final String name; @NotBlank(message = "행사의 장소를 입력해 주세요.") @@ -30,31 +29,29 @@ public class EventDetailRequest { @NotBlank(message = "행사의 상세 URL을 입력해 주세요.") @Pattern(regexp = "(http.?://).*", message = "http:// 혹은 https://로 시작하는 주소를 입력해 주세요.") private final String informationUrl; - + @DateTimeFormat(pattern = DATE_TIME_FORMAT) @NotNull(message = "행사의 시작 일시를 입력해 주세요.") private final LocalDateTime startDateTime; @DateTimeFormat(pattern = DATE_TIME_FORMAT) @NotNull(message = "행사의 종료 일시를 입력해 주세요.") private final LocalDateTime endDateTime; - + @DateTimeFormat(pattern = DATE_TIME_FORMAT) private final LocalDateTime applyStartDateTime; @DateTimeFormat(pattern = DATE_TIME_FORMAT) private final LocalDateTime applyEndDateTime; - + private final List tags; - + private final String imageUrl; private final EventType type; - + private final EventMode eventMode; private final PaymentType paymentType; - - private final List images; - + private final String organization; - + public Event toEvent() { return new Event( name, diff --git a/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java b/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java index 20ab82a78..446adfb38 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java @@ -69,7 +69,7 @@ import org.springframework.web.multipart.MultipartFile; class EventServiceTest extends ServiceIntegrationTestHelper { - + private static final EventResponse 인프콘_2023 = new EventResponse(null, "인프콘 2023", null, null, List.of(), "IN_PROGRESS", "ENDED", null, 0, 0, EventMode.OFFLINE.getValue(), PaymentType.PAID.getValue()); @@ -92,8 +92,8 @@ class EventServiceTest extends ServiceIntegrationTestHelper { private static final EventResponse 구름톤 = new EventResponse(null, "구름톤", null, null, List.of(), "IN_PROGRESS", "IN_PROGRESS", null, 0, 0, EventMode.ONLINE.getValue(), PaymentType.PAID.getValue()); - - + + private static final LocalDate TODAY = LocalDate.of(2023, 7, 21); @Autowired private EventService eventService; @@ -105,9 +105,9 @@ class EventServiceTest extends ServiceIntegrationTestHelper { private TagRepository tagRepository; @Autowired private ImageRepository imageRepository; - + private List mockMultipartFiles; - + @BeforeEach void init() { final Tag 백엔드 = tagRepository.save(백엔드()); @@ -115,7 +115,7 @@ void init() { final Tag 안드로이드 = tagRepository.save(안드로이드()); final Tag IOS = tagRepository.save(IOS()); final Tag AI = tagRepository.save(AI()); - + final Event 인프콘_2023 = eventRepository.save(인프콘_2023()); final Event AI_컨퍼런스 = eventRepository.save(AI_컨퍼런스()); final Event 모바일_컨퍼런스 = eventRepository.save(모바일_컨퍼런스()); @@ -123,14 +123,14 @@ void init() { final Event 웹_컨퍼런스 = eventRepository.save(웹_컨퍼런스()); final Event AI_아이디어_공모전 = eventRepository.save(AI_아이디어_공모전()); final Event 구름톤 = eventRepository.save(구름톤()); - + eventTagRepository.saveAll(List.of( new EventTag(인프콘_2023, 백엔드), new EventTag(인프콘_2023, 프론트엔드), new EventTag(인프콘_2023, 안드로이드), new EventTag(인프콘_2023, IOS), new EventTag(인프콘_2023, AI), new EventTag(AI_컨퍼런스, AI), new EventTag(모바일_컨퍼런스, 안드로이드), new EventTag(모바일_컨퍼런스, IOS), new EventTag(안드로이드_컨퍼런스, 안드로이드), new EventTag(웹_컨퍼런스, 백엔드), new EventTag(웹_컨퍼런스, 프론트엔드)) ); - + mockMultipartFiles = List.of( new MockMultipartFile( "picture", @@ -140,11 +140,11 @@ void init() { ) ); } - + @Nested @DisplayName("id로 이벤트를 조회할 수 있다.") class findEventTest { - + @Test @DisplayName("event의 id로 해당하는 event를 조회할 수 있다.") void success() { @@ -156,185 +156,185 @@ void success() { imageRepository.save( new Image("imageUrl2", ImageType.EVENT, event.getId(), 0, LocalDateTime.now()) ); - + final List imageUrls = List.of("imageUrl2", "imageUrl1"); - + final EventDetailResponse expected = EventDetailResponse.from(event, 날짜_8월_10일(), imageUrls); - + //when final EventDetailResponse actual = eventService.findEvent(event.getId(), 날짜_8월_10일()); - + //then assertThat(actual) .usingRecursiveComparison() .isEqualTo(expected); } - + @Test @DisplayName("요청한 id에 해당하는 event가 존재하지 않으면 Exception을 던진다.") void fail_EventNotFoundException() { //given final Long notFoundEventId = Long.MAX_VALUE; - + //when, then assertThatThrownBy(() -> eventService.findEvent(notFoundEventId, 날짜_8월_10일())) .isInstanceOf(EventException.class) .hasMessage(NOT_FOUND_EVENT.errorMessage()); } } - + @Nested @DisplayName("findEvents() : 행사 목록 조회") class findEvents { - + @Test @DisplayName("2023년 7월 21일에 컨퍼런스 행사를 조회하면, 해당 카테고리에 해당하는 모든 행사 목록을 조회할 수 있다.") void findEvents_CONFERENCE() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 대회 행사를 조회하면, 해당 카테고리에 해당하는 모든 행사 목록을 조회할 수 있다.") void findEvents_COMPETITION() { // given final List expectedEvents = List.of(구름톤, AI_아이디어_공모전); - + // when final List actualEvents = eventService.findEvents(EventType.COMPETITION, TODAY, null, null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_7() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-01", "2023-07-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 8월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_8() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-08-01", "2023-08-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 6월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_6() { // given final List expectedEvents = List.of(인프콘_2023, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-06-01", "2023-06-30", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 17일 이후에 있는 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_after_2023_7_17() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-17", null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 31일 이전에 있는 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_before_2023_7_31() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, "2023-07-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @ParameterizedTest @NullSource @DisplayName("등록된 행사가 없고 status 옵션이 없을 경우 빈 목록을 반환한다.") void findEvents_empty(final List statusName) { // given eventRepository.deleteAll(); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, statusName); - + // then assertThat(actualEvents).isEmpty(); } - + @Test @DisplayName("아무 행사도 없는 2023년 12월 행사를 조회하면, 빈 목록을 반환한다.") void findEvents_2023_12() { // given, when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, null); - + // then assertThat(actualEvents).isEmpty(); } - - + + @Test @DisplayName("아무 행사도 없는 2023년 12월의 행사를 tag로 필터링하면, 빈 목록을 반환한다.") void findEvents_empty_tag_filter() { @@ -342,11 +342,11 @@ void findEvents_empty_tag_filter() { final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", List.of("안드로이드"), null); - + // then assertThat(actualEvents).isEmpty(); } - + @Test @DisplayName("아무 행사도 없는 2023년 12월의 행사를 status로 필터링하면, 빈 목록을 반환한다.") void findEvents_empty_status_filter() { @@ -354,11 +354,11 @@ void findEvents_empty_status_filter() { final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, List.of(IN_PROGRESS)); - + // then assertThat(actualEvents).isEmpty(); } - + @ParameterizedTest @ValueSource(strings = {"abcde", "00-0-0", "-1-1-1-1", "2023-02-30"}) @DisplayName("유효하지 않은 값이 시작일 정보로 들어오면 예외를 반환한다.") @@ -366,13 +366,13 @@ void findEvents_start_date_fail(final String startDate) { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, startDate, "2023-07-31", null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(INVALID_DATE_FORMAT.errorMessage()); } - + @ParameterizedTest @ValueSource(strings = {"abcde", "00-0-0", "-1-1-1-1", "2023-02-30"}) @DisplayName("유효하지 않은 값이 종료일 값으로 들어오면 예외를 반환한다.") @@ -380,122 +380,122 @@ void findEvents_end_date_fail(final String endDate) { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-01", endDate, null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(INVALID_DATE_FORMAT.errorMessage()); } - + @Test @DisplayName("시작일이 종료일보다 뒤에 있으면 예외를 반환한다.") void findEvents_start_after_end_fail() { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-16", "2023-07-15", null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(START_DATE_AFTER_END_DATE.errorMessage()); } - + @Test @DisplayName("'안드로이드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_tag_filter() { // given final List expectedEvents = List.of(인프콘_2023, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("안드로이드"), null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("'안드로이드', '백엔드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_tags_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("안드로이드", "백엔드"), null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("존재하지 않는 태그가 입력으로 들어오면 예외를 반환한다.") void findEvents_tag_filter_fail() { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("개발"), null); - + // then assertThatThrownBy(actual) .isInstanceOf(TagException.class) .hasMessage(TagExceptionType.NOT_FOUND_TAG.errorMessage()); } - + @Test @DisplayName("'진행 중' 상태의 행사 목록을 조회할 수 있다.") void findEvents_status_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, List.of(IN_PROGRESS)); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("'진행 중' 및 '진행 예정' 상태의 행사 목록을 조회할 수 있다.") void findEvents_statuses_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, List.of(EventStatus.UPCOMING, IN_PROGRESS)); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("9월에 존재하는 진행 예정인 '안드로이드', '백엔드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_period_tags_filter() { // given final List expectedEvents = List.of(모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-09-01", "2023-09-30", List.of("안드로이드", "백엔드"), List.of(EventStatus.UPCOMING)); - + // then assertThat(actualEvents) .usingRecursiveComparison() @@ -503,10 +503,10 @@ void findEvents_period_tags_filter() { .isEqualTo(expectedEvents); } } - + @Nested class AddEvent { - + final List tagRequests = List.of( new TagRequest(IOS().getName()), new TagRequest(AI().getName()) @@ -522,14 +522,14 @@ class AddEvent { private final EventType type = EventType.CONFERENCE; private final LocalDate now = LocalDate.now(); private final String organization = "행사기관"; - + @Test @DisplayName("이벤트를 성공적으로 저장한다.") void addEventTest() { //given final Image image1 = new Image("image", ImageType.EVENT, 1L, 0, LocalDateTime.now()); final Image image2 = new Image("image", ImageType.EVENT, 1L, 0, LocalDateTime.now()); - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -543,16 +543,15 @@ void addEventTest() { type, eventMode, paymentType, - mockMultipartFiles, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when - final EventDetailResponse response = eventService.addEvent(request, now); + final EventDetailResponse response = eventService.addEvent(request, mockMultipartFiles, now); final Event savedEvent = eventRepository.findById(response.getId()).get(); - + //then assertAll( () -> assertEquals(eventName, savedEvent.getName()), @@ -568,14 +567,14 @@ void addEventTest() { .collect(Collectors.toList()) ); } - + @Test @DisplayName("행사 시작 일시가 행사 종료 일시 이후일 경우 EventException이 발생한다.") void addEventWithStartDateTimeAfterBeforeDateTimeTest() { //given final LocalDateTime startDateTime = afterDateTime; final LocalDateTime endDatetime = beforeDateTime; - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -589,19 +588,18 @@ void addEventWithStartDateTimeAfterBeforeDateTimeTest() { type, eventMode, paymentType, - mockMultipartFiles, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, - () -> eventService.addEvent(request, now)); - + () -> eventService.addEvent(request, mockMultipartFiles, now)); + assertEquals(exception.exceptionType(), START_DATE_TIME_AFTER_END_DATE_TIME); } - + @Test @DisplayName("Tag가 존재하지 않을 경우 EventException이 발생한다.") void addEventWithNotExistTagTest() { @@ -611,7 +609,7 @@ void addEventWithNotExistTagTest() { new TagRequest(안드로이드().getName()), new TagRequest("존재하지 않는 태그") ); - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -625,23 +623,22 @@ void addEventWithNotExistTagTest() { type, eventMode, paymentType, - mockMultipartFiles, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, - () -> eventService.addEvent(request, now)); - + () -> eventService.addEvent(request, mockMultipartFiles, now)); + assertEquals(exception.exceptionType(), NOT_FOUND_TAG); } } - + @Nested class UpdateEvent { - + final List newTagRequests = List.of( new TagRequest(IOS().getName()), new TagRequest(AI().getName()) @@ -656,14 +653,14 @@ class UpdateEvent { private final PaymentType paymentType = PaymentType.FREE_PAID; private final EventMode eventMode = EventMode.ON_OFFLINE; private final String organization = "행사기관"; - + @Test @DisplayName("이벤트를 성공적으로 업데이트한다.") void updateEventTest() { //given final LocalDateTime newStartDateTime = beforeDateTime; final LocalDateTime newEndDateTime = afterDateTime; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -677,17 +674,17 @@ void updateEventTest() { EventType.CONFERENCE, eventMode, paymentType, - mockMultipartFiles, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when - final EventDetailResponse response = eventService.updateEvent(eventId, updateRequest, now); + final EventDetailResponse response = eventService.updateEvent(eventId, updateRequest, + mockMultipartFiles, now); final Event updatedEvent = eventRepository.findById(eventId).get(); - + //then assertAll( () -> assertEquals(newName, updatedEvent.getName()), @@ -703,13 +700,13 @@ void updateEventTest() { .collect(Collectors.toList()) ); } - + @Test @DisplayName("업데이트할 이벤트가 존재하지 않을 경우 EventException이 발생한다.") void updateEventWithNotExistsEventTest() { //given final long notExistsEventId = 0L; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -723,24 +720,23 @@ void updateEventWithNotExistsEventTest() { EventType.CONFERENCE, eventMode, paymentType, - mockMultipartFiles, organization ); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, - () -> eventService.updateEvent(notExistsEventId, updateRequest, now)); - + () -> eventService.updateEvent(notExistsEventId, updateRequest, mockMultipartFiles, now)); + assertEquals(exception.exceptionType(), NOT_FOUND_EVENT); } - + @Test @DisplayName("행사 시작 일시가 행사 종료 일시 이후일 경우 EventException이 발생한다.") void updateEventWithStartDateTimeAfterBeforeDateTimeTest() { //given final LocalDateTime newStartDateTime = afterDateTime; final LocalDateTime newEndDateTime = beforeDateTime; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -754,20 +750,19 @@ void updateEventWithStartDateTimeAfterBeforeDateTimeTest() { EventType.CONFERENCE, eventMode, paymentType, - mockMultipartFiles, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, - () -> eventService.updateEvent(eventId, updateRequest, now)); - + () -> eventService.updateEvent(eventId, updateRequest, mockMultipartFiles, now)); + assertEquals(exception.exceptionType(), START_DATE_TIME_AFTER_END_DATE_TIME); } - + @Test @DisplayName("Tag가 존재하지 않을 경우 EventException이 발생한다.") void updateEventWithNotExistTagTest() { @@ -775,7 +770,7 @@ void updateEventWithNotExistTagTest() { final List newTagRequests = List.of( new TagRequest("존재하지 않는 태그") ); - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -789,48 +784,47 @@ void updateEventWithNotExistTagTest() { EventType.CONFERENCE, eventMode, paymentType, - mockMultipartFiles, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, - () -> eventService.updateEvent(eventId, updateRequest, now)); - + () -> eventService.updateEvent(eventId, updateRequest, mockMultipartFiles, now)); + assertEquals(exception.exceptionType(), NOT_FOUND_TAG); } } - + @Nested class DeleteEvent { - + @Test @DisplayName("이벤트를 성공적으로 삭제한다.") void deleteEventTest() { //given final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when eventService.deleteEvent(eventId); - + //then assertFalse(eventRepository.findById(eventId).isPresent()); } - + @Test @DisplayName("삭제할 이벤트가 존재하지 않을 경우 EventException이 발생한다.") void deleteEventWithNotExistsEventTest() { //given final long notExistsEventId = 0L; - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.deleteEvent(notExistsEventId)); - + assertEquals(exception.exceptionType(), NOT_FOUND_EVENT); } } From 7d99b132d55ef77c434866dedfea3b4df91002e8 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Sat, 23 Sep 2023 16:10:29 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=ED=96=89=EC=82=AC=EC=97=90=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=ED=95=98=EB=8A=94=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20=EA=B0=9C=EC=88=98=20=EC=A0=9C=ED=95=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #633 --- .../java/com/emmsale/image/domain/ImageType.java | 2 +- .../application/ImageCommandServiceTest.java | 16 +++++++++++++--- .../com/emmsale/image/domain/ImageTypeTest.java | 2 +- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java index 49ede5292..4c6b0ad51 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java +++ b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java @@ -2,7 +2,7 @@ public enum ImageType { FEED(5), - EVENT(2); + EVENT(200); private final int maxImageCount; diff --git a/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java b/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java index 9f2797937..436e6ae93 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java @@ -1,6 +1,7 @@ package com.emmsale.image.application; import static com.emmsale.event.EventFixture.인프콘_2023; +import static com.emmsale.member.MemberFixture.memberFixture; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -13,6 +14,7 @@ import com.emmsale.event.domain.repository.EventRepository; import com.emmsale.event.exception.EventException; import com.emmsale.event.exception.EventExceptionType; +import com.emmsale.feed.domain.Feed; import com.emmsale.feed.domain.repository.FeedRepository; import com.emmsale.feed.exception.FeedException; import com.emmsale.feed.exception.FeedExceptionType; @@ -22,6 +24,8 @@ import com.emmsale.image.domain.repository.ImageRepository; import com.emmsale.image.exception.ImageException; import com.emmsale.image.exception.ImageExceptionType; +import com.emmsale.member.domain.Member; +import com.emmsale.member.domain.MemberRepository; import java.util.List; import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.BeforeEach; @@ -44,6 +48,8 @@ class ImageCommandServiceTest extends ServiceIntegrationTestHelper { private EventRepository eventRepository; @Autowired private FeedRepository feedRepository; + @Autowired + private MemberRepository memberRepository; private S3Client s3Client; @BeforeEach @@ -140,15 +146,19 @@ void saveImages_fail_not_found_feed() { void saveImages_fail_over_max_image_count() { //given final Event event = eventRepository.save(인프콘_2023()); + final Member member = memberRepository.save(memberFixture()); + final Feed feed = feedRepository.save(new Feed(event, member, "피드", "피드 내용")); final List files = List.of( new MockMultipartFile("test", "test1.png", "", new byte[]{}), new MockMultipartFile("test", "test2.png", "", new byte[]{}), new MockMultipartFile("test", "test3.png", "", new byte[]{}), - new MockMultipartFile("test", "test4.png", "", new byte[]{})); + new MockMultipartFile("test", "test4.png", "", new byte[]{}), + new MockMultipartFile("test", "test5.png", "", new byte[]{}), + new MockMultipartFile("test", "test6.png", "", new byte[]{})); //when - final ThrowingCallable actual = () -> imageCommandService.saveImages(ImageType.EVENT, - event.getId(), files); + final ThrowingCallable actual = () -> imageCommandService.saveImages(ImageType.FEED, + feed.getId(), files); //then assertThatThrownBy(actual).isInstanceOf(ImageException.class) diff --git a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java index 2153b79a9..e73484cca 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java @@ -9,7 +9,7 @@ class ImageTypeTest { @ParameterizedTest - @CsvSource(value = {"FEED:3:false", "EVENT:3:true"}, delimiter = ':') + @CsvSource(value = {"FEED:3:false", "FEED:6:true"}, delimiter = ':') @DisplayName("isOverMaxImageCount(): 입력받은 값이 이미지 유형의 최대 이미지 수보다 큰지 여부를 반환한다.") void isOverMaxImageCount(final ImageType type, final int imageCount, final boolean expected) { //given, when From d3aba1bedeeb4e3a40ba2b787807c865e9179608 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Sat, 23 Sep 2023 16:12:22 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=EB=88=84=EB=9D=BD=EB=90=9C=20f?= =?UTF-8?q?inal=20=ED=82=A4=EC=9B=8C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #633 --- .../src/main/java/com/emmsale/event/api/EventApi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java index 9803f0394..4eaeb2f33 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java @@ -51,7 +51,7 @@ public ResponseEntity> findEvents( @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.CREATED) public EventDetailResponse addEvent(@RequestPart @Valid final EventDetailRequest request, - @RequestPart List images) { + @RequestPart final List images) { return eventService.addEvent(request, images, LocalDate.now()); } @@ -59,7 +59,7 @@ public EventDetailResponse addEvent(@RequestPart @Valid final EventDetailRequest @ResponseStatus(HttpStatus.OK) public EventDetailResponse updateEvent(@PathVariable final Long eventId, @RequestPart @Valid final EventDetailRequest request, - @RequestPart List images) { + @RequestPart final List images) { return eventService.updateEvent(eventId, request, images, LocalDate.now()); } From ab626dac74ad7de533b3f419f08904a427dcf45a Mon Sep 17 00:00:00 2001 From: amaran-th Date: Mon, 25 Sep 2023 15:41:28 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 줄바꿈 위치에 공백이 생기던 문제 해결 #633 --- .../java/com/emmsale/EventApiTest.java | 122 ++++----- .../application/dto/EventDetailRequest.java | 16 +- .../com/emmsale/image/domain/ImageType.java | 8 +- .../event/application/EventServiceTest.java | 232 +++++++++--------- .../application/ImageCommandServiceTest.java | 50 ++-- .../emmsale/image/domain/ImageTypeTest.java | 4 +- 6 files changed, 216 insertions(+), 216 deletions(-) diff --git a/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java b/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java index 6322ffe99..5d919b0ce 100644 --- a/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java +++ b/backend/emm-sale/src/documentTest/java/com/emmsale/EventApiTest.java @@ -57,7 +57,7 @@ @WebMvcTest(EventApi.class) class EventApiTest extends MockMvcTestHelper { - + private static final ResponseFieldsSnippet EVENT_DETAIL_RESPONSE_FILED = PayloadDocumentation.responseFields( fieldWithPath("id").type(JsonFieldType.NUMBER).description("event 식별자"), fieldWithPath("name").type(JsonFieldType.STRING) @@ -88,7 +88,7 @@ class EventApiTest extends MockMvcTestHelper { fieldWithPath("organization").description("행사기관"), fieldWithPath("paymentType").description("유무료 여부(유료,무료,유무료)") ); - + @Test @DisplayName("컨퍼런스의 상세정보를 조회할 수 있다.") void findEvent() throws Exception { @@ -105,13 +105,13 @@ void findEvent() throws Exception { Mockito.when(eventService.findEvent(ArgumentMatchers.anyLong(), any())) .thenReturn(eventDetailResponse); - + //when mockMvc.perform(get("/events/" + eventId)).andExpect( status().isOk()) .andDo(MockMvcRestDocumentation.document("find-event", EVENT_DETAIL_RESPONSE_FILED)); } - + @Test @DisplayName("특정 카테고리의 행사 목록을 조회할 수 있으면 200 OK를 반환한다.") void findEvents() throws Exception { @@ -129,7 +129,7 @@ void findEvents() throws Exception { .description("필터링하려는 상태(UPCOMING, IN_PROGRESS, ENDED)(option)") .optional() ); - + final ResponseFieldsSnippet responseFields = PayloadDocumentation.responseFields( fieldWithPath("[].id").type(JsonFieldType.NUMBER).description("행사 id"), fieldWithPath("[].name").type(JsonFieldType.STRING).description("행사명"), @@ -154,7 +154,7 @@ void findEvents() throws Exception { fieldWithPath("[].paymentType").type(JsonFieldType.STRING) .description("행사 유료 여부(유료, 무료, 유무료)") ); - + final List eventResponses = List.of( new EventResponse(1L, "인프콘 2023", LocalDateTime.parse("2023-06-03T12:00:00"), LocalDateTime.parse("2023-09-03T12:00:00"), @@ -172,12 +172,12 @@ void findEvents() throws Exception { "https://biz.pusan.ac.kr/dext5editordata/2022/08/20220810_160546511_10103.jpg", 3, -18, EventMode.ONLINE.getValue(), PaymentType.PAID.getValue()) ); - + Mockito.when(eventService.findEvents(any(EventType.class), any(LocalDate.class), eq("2023-07-01"), eq("2023-07-31"), eq(null), any())).thenReturn(eventResponses); - + // when & then mockMvc.perform(get("/events") .param("category", "CONFERENCE") @@ -188,7 +188,7 @@ void findEvents() throws Exception { .andExpect(status().isOk()) .andDo(MockMvcRestDocumentation.document("find-events", requestParameters, responseFields)); } - + @Test @DisplayName("이벤트를 성공적으로 업데이트하면 200, OK를 반환한다.") void updateEventTest() throws Exception { @@ -199,7 +199,7 @@ void updateEventTest() throws Exception { MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", @@ -208,30 +208,30 @@ void updateEventTest() throws Exception { ); final long eventId = 1L; final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - + final EventDetailRequest request = new EventDetailRequest(event.getName(), event.getLocation(), event.getInformationUrl(), event.getEventPeriod().getStartDate(), event.getEventPeriod().getEndDate(), event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), tags, event.getImageUrl(), event.getType(), EventMode.ON_OFFLINE, PaymentType.FREE, "행사기관"); - + final EventDetailResponse response = new EventDetailResponse(1L, request.getName(), request.getInformationUrl(), request.getStartDateTime(), request.getEndDateTime(), request.getApplyStartDateTime(), request.getApplyEndDateTime(), request.getLocation(), EventStatus.IN_PROGRESS.name(), EventStatus.ENDED.name(), tags.stream().map(TagRequest::getName).collect(Collectors.toList()), request.getImageUrl(), 10, 10, request.getType().toString(), - List.of("imageUrl1", "imageUrl2"), "행사기관","유료"); - + List.of("imageUrl1", "imageUrl2"), "행사기관", "유료"); + Mockito.when(eventService.updateEvent(eq(eventId), any(EventDetailRequest.class), any(), any())) .thenReturn(response); - + String contents = objectMapper.writeValueAsString(request); - + final RequestPartsSnippet requestPartsSnippet = requestParts( partWithName("images").description("이미지들").optional(), partWithName("request").description("행사 정보들"), @@ -251,45 +251,45 @@ void updateEventTest() throws Exception { .optional(), partWithName("request.organization").description("행사 주최 기관").optional() ); - + //when MockMultipartHttpServletRequestBuilder builder = multipart(HttpMethod.PUT, "/events/" + eventId) .file("images", image1.getBytes()) .file("images", image2.getBytes()) .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( StandardCharsets.UTF_8))); - + final ResultActions result = mockMvc.perform(builder); - + //when & then result.andExpect(status().isOk()) .andDo(MockMvcResultHandlers.print()) .andDo(MockMvcRestDocumentation.document("update-event", requestPartsSnippet, EVENT_DETAIL_RESPONSE_FILED)); - - + + } - + @Test @DisplayName("이벤트를 성공적으로 삭제하면 204, NO_CONTENT를 반환한다.") void deleteEventTest() throws Exception { //given final long eventId = 1L; - + Mockito.doNothing().when(eventService).deleteEvent(eventId); //when final ResultActions result = mockMvc.perform( delete("/events/" + eventId)); - + //then result.andExpect(status().isNoContent()) .andDo(MockMvcResultHandlers.print()).andDo( MockMvcRestDocumentation.document("delete-event")); } - + @Nested class AddEvent { - + @Test @DisplayName("이벤트를 성공적으로 추가하면 201, CREATED 를 반환한다.") void addEventTest() throws Exception { @@ -300,39 +300,39 @@ void addEventTest() throws Exception { MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); - + final EventDetailRequest request = new EventDetailRequest(event.getName(), event.getLocation(), event.getInformationUrl(), event.getEventPeriod().getStartDate(), event.getEventPeriod().getEndDate(), event.getEventPeriod().getApplyStartDate(), event.getEventPeriod().getApplyEndDate(), tags, event.getImageUrl(), event.getType(), EventMode.ON_OFFLINE, PaymentType.FREE, "행사기관"); - + final EventDetailResponse response = new EventDetailResponse(1L, request.getName(), request.getInformationUrl(), request.getStartDateTime(), request.getEndDateTime(), request.getApplyStartDateTime(), request.getApplyEndDateTime(), request.getLocation(), EventStatus.IN_PROGRESS.name(), EventStatus.ENDED.name(), tags.stream().map(TagRequest::getName).collect(Collectors.toList()), request.getImageUrl(), 10, 10, request.getType().toString(), - List.of("imageUrl1", "imageUrl2"), "행사기관","무료"); - + List.of("imageUrl1", "imageUrl2"), "행사기관", "무료"); + Mockito.when(eventService.addEvent(any(EventDetailRequest.class), any(), any())) .thenReturn(response); - + String contents = objectMapper.writeValueAsString(request); - + final RequestPartsSnippet requestPartsSnippet = requestParts( partWithName("images").description("이미지들").optional(), partWithName("request").description("행사 정보들"), @@ -352,23 +352,23 @@ void addEventTest() throws Exception { .optional(), partWithName("request.organization").description("행사 주최 기관").optional() ); - + //when MockMultipartHttpServletRequestBuilder builder = multipart("/events") .file("images", image1.getBytes()) .file("images", image2.getBytes()) .file(new MockMultipartFile("request", "", "application/json", contents.getBytes( StandardCharsets.UTF_8))); - + final ResultActions result = mockMvc.perform(builder); - + //then result.andExpect(status().isCreated()) .andDo(MockMvcResultHandlers.print()) .andDo(MockMvcRestDocumentation.document("add-event", EVENT_DETAIL_RESPONSE_FILED, requestPartsSnippet)); } - + @ParameterizedTest @NullSource @EmptySource @@ -381,16 +381,16 @@ void addEventWithEmptyNameTest(final String eventName) throws Exception { MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); final EventDetailRequest request = new EventDetailRequest( @@ -409,7 +409,7 @@ void addEventWithEmptyNameTest(final String eventName) throws Exception { ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @NullSource @EmptySource @@ -422,16 +422,16 @@ void addEventWithEmptyLocationTest(final String eventLocation) throws Exception MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); final EventDetailRequest request = new EventDetailRequest( @@ -450,7 +450,7 @@ void addEventWithEmptyLocationTest(final String eventLocation) throws Exception ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @ValueSource(strings = {"httpexample.com", "http:example.com", "http:/example.com", "httpsexample.com", "https:example.com", "https:/example.com"}) @@ -464,16 +464,16 @@ void addEventWithInvalidInformationUrlTest(final String informationUrl) throws E MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + final List tags = Stream.of(TagFixture.백엔드(), TagFixture.안드로이드()) .map(tag -> new TagRequest(tag.getName())).collect(Collectors.toList()); final EventDetailRequest request = new EventDetailRequest( @@ -492,7 +492,7 @@ void addEventWithInvalidInformationUrlTest(final String informationUrl) throws E ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @ValueSource(strings = {"23-01-01T12:00:00", "2023-1-01T12:00:00", "2023-01-1T12:00:00", "2023-01-01T2:00:00", "2023-01-01T12:0:00", "2023-01-01T12:00:0"}) @@ -507,16 +507,16 @@ void addEventWithUnformattedStartDateTimeTest(final String startDateTime) MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + Map request = new HashMap<>(); request.put("name", event.getName()); request.put("location", event.getLocation()); @@ -530,7 +530,7 @@ void addEventWithUnformattedStartDateTimeTest(final String startDateTime) request.put("eventMode", event.getEventMode().name()); request.put("paymentType", event.getPaymentType().name()); request.put("organization", event.getOrganization()); - + String contents = objectMapper.writeValueAsString(request); //when & then mockMvc.perform(multipart("/events") @@ -541,7 +541,7 @@ void addEventWithUnformattedStartDateTimeTest(final String startDateTime) ) .andExpect(status().isBadRequest()); } - + @ParameterizedTest @ValueSource(strings = {"23-01-02T12:00:00", "2023-1-02T12:00:00", "2023-01-2T12:00:00", "2023-01-02T2:00:00", "2023-01-02T12:0:00", "2023-01-02T12:00:0"}) @@ -555,16 +555,16 @@ void addEventWithUnformattedEndDateTimeTest(final String endDateTime) throws Exc MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final MockMultipartFile image2 = new MockMultipartFile( "picture", "picture.jpg", MediaType.TEXT_PLAIN_VALUE, "test data".getBytes() ); - + final Event event = EventFixture.인프콘_2023(); - + Map request = new HashMap<>(); request.put("name", event.getName()); request.put("location", event.getLocation()); @@ -578,7 +578,7 @@ void addEventWithUnformattedEndDateTimeTest(final String endDateTime) throws Exc request.put("eventMode", event.getEventMode().name()); request.put("paymentType", event.getPaymentType().name()); request.put("organization", event.getOrganization()); - + String contents = objectMapper.writeValueAsString(request); //when & then mockMvc.perform(multipart("/events") diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java index 29e4c20e8..722d5c044 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java @@ -19,9 +19,9 @@ @Getter @Setter public class EventDetailRequest { - + private static final String DATE_TIME_FORMAT = "yyyy:MM:dd:HH:mm:ss"; - + @NotBlank(message = "행사의 이름을 입력해 주세요.") private final String name; @NotBlank(message = "행사의 장소를 입력해 주세요.") @@ -29,27 +29,27 @@ public class EventDetailRequest { @NotBlank(message = "행사의 상세 URL을 입력해 주세요.") @Pattern(regexp = "(http.?://).*", message = "http:// 혹은 https://로 시작하는 주소를 입력해 주세요.") private final String informationUrl; - + @DateTimeFormat(pattern = DATE_TIME_FORMAT) @NotNull(message = "행사의 시작 일시를 입력해 주세요.") private final LocalDateTime startDateTime; @DateTimeFormat(pattern = DATE_TIME_FORMAT) @NotNull(message = "행사의 종료 일시를 입력해 주세요.") private final LocalDateTime endDateTime; - + @DateTimeFormat(pattern = DATE_TIME_FORMAT) private final LocalDateTime applyStartDateTime; @DateTimeFormat(pattern = DATE_TIME_FORMAT) private final LocalDateTime applyEndDateTime; - + private final List tags; - + private final String imageUrl; private final EventType type; - + private final EventMode eventMode; private final PaymentType paymentType; - + private final String organization; public Event toEvent() { diff --git a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java index 4c6b0ad51..00f5380cd 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java +++ b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java @@ -3,15 +3,15 @@ public enum ImageType { FEED(5), EVENT(200); - + private final int maxImageCount; - + ImageType(final int maxImageCount) { this.maxImageCount = maxImageCount; } - + public boolean isOverMaxImageCount(final int imageCount) { return imageCount > maxImageCount; } - + } diff --git a/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java b/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java index 446adfb38..3de8d1caa 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/event/application/EventServiceTest.java @@ -69,7 +69,7 @@ import org.springframework.web.multipart.MultipartFile; class EventServiceTest extends ServiceIntegrationTestHelper { - + private static final EventResponse 인프콘_2023 = new EventResponse(null, "인프콘 2023", null, null, List.of(), "IN_PROGRESS", "ENDED", null, 0, 0, EventMode.OFFLINE.getValue(), PaymentType.PAID.getValue()); @@ -92,8 +92,8 @@ class EventServiceTest extends ServiceIntegrationTestHelper { private static final EventResponse 구름톤 = new EventResponse(null, "구름톤", null, null, List.of(), "IN_PROGRESS", "IN_PROGRESS", null, 0, 0, EventMode.ONLINE.getValue(), PaymentType.PAID.getValue()); - - + + private static final LocalDate TODAY = LocalDate.of(2023, 7, 21); @Autowired private EventService eventService; @@ -105,9 +105,9 @@ class EventServiceTest extends ServiceIntegrationTestHelper { private TagRepository tagRepository; @Autowired private ImageRepository imageRepository; - + private List mockMultipartFiles; - + @BeforeEach void init() { final Tag 백엔드 = tagRepository.save(백엔드()); @@ -115,7 +115,7 @@ void init() { final Tag 안드로이드 = tagRepository.save(안드로이드()); final Tag IOS = tagRepository.save(IOS()); final Tag AI = tagRepository.save(AI()); - + final Event 인프콘_2023 = eventRepository.save(인프콘_2023()); final Event AI_컨퍼런스 = eventRepository.save(AI_컨퍼런스()); final Event 모바일_컨퍼런스 = eventRepository.save(모바일_컨퍼런스()); @@ -123,14 +123,14 @@ void init() { final Event 웹_컨퍼런스 = eventRepository.save(웹_컨퍼런스()); final Event AI_아이디어_공모전 = eventRepository.save(AI_아이디어_공모전()); final Event 구름톤 = eventRepository.save(구름톤()); - + eventTagRepository.saveAll(List.of( new EventTag(인프콘_2023, 백엔드), new EventTag(인프콘_2023, 프론트엔드), new EventTag(인프콘_2023, 안드로이드), new EventTag(인프콘_2023, IOS), new EventTag(인프콘_2023, AI), new EventTag(AI_컨퍼런스, AI), new EventTag(모바일_컨퍼런스, 안드로이드), new EventTag(모바일_컨퍼런스, IOS), new EventTag(안드로이드_컨퍼런스, 안드로이드), new EventTag(웹_컨퍼런스, 백엔드), new EventTag(웹_컨퍼런스, 프론트엔드)) ); - + mockMultipartFiles = List.of( new MockMultipartFile( "picture", @@ -140,11 +140,11 @@ void init() { ) ); } - + @Nested @DisplayName("id로 이벤트를 조회할 수 있다.") class findEventTest { - + @Test @DisplayName("event의 id로 해당하는 event를 조회할 수 있다.") void success() { @@ -156,185 +156,185 @@ void success() { imageRepository.save( new Image("imageUrl2", ImageType.EVENT, event.getId(), 0, LocalDateTime.now()) ); - + final List imageUrls = List.of("imageUrl2", "imageUrl1"); - + final EventDetailResponse expected = EventDetailResponse.from(event, 날짜_8월_10일(), imageUrls); - + //when final EventDetailResponse actual = eventService.findEvent(event.getId(), 날짜_8월_10일()); - + //then assertThat(actual) .usingRecursiveComparison() .isEqualTo(expected); } - + @Test @DisplayName("요청한 id에 해당하는 event가 존재하지 않으면 Exception을 던진다.") void fail_EventNotFoundException() { //given final Long notFoundEventId = Long.MAX_VALUE; - + //when, then assertThatThrownBy(() -> eventService.findEvent(notFoundEventId, 날짜_8월_10일())) .isInstanceOf(EventException.class) .hasMessage(NOT_FOUND_EVENT.errorMessage()); } } - + @Nested @DisplayName("findEvents() : 행사 목록 조회") class findEvents { - + @Test @DisplayName("2023년 7월 21일에 컨퍼런스 행사를 조회하면, 해당 카테고리에 해당하는 모든 행사 목록을 조회할 수 있다.") void findEvents_CONFERENCE() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 대회 행사를 조회하면, 해당 카테고리에 해당하는 모든 행사 목록을 조회할 수 있다.") void findEvents_COMPETITION() { // given final List expectedEvents = List.of(구름톤, AI_아이디어_공모전); - + // when final List actualEvents = eventService.findEvents(EventType.COMPETITION, TODAY, null, null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_7() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-01", "2023-07-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 8월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_8() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-08-01", "2023-08-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 6월 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_2023_6() { // given final List expectedEvents = List.of(인프콘_2023, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-06-01", "2023-06-30", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 17일 이후에 있는 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_after_2023_7_17() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-17", null, null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("2023년 7월 21일에 2023년 7월 31일 이전에 있는 행사를 조회하면, 해당 기간에 걸쳐있는 모든 행사 목록을 조회할 수 있다.") void findEvents_before_2023_7_31() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, "2023-07-31", null, null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @ParameterizedTest @NullSource @DisplayName("등록된 행사가 없고 status 옵션이 없을 경우 빈 목록을 반환한다.") void findEvents_empty(final List statusName) { // given eventRepository.deleteAll(); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, statusName); - + // then assertThat(actualEvents).isEmpty(); } - + @Test @DisplayName("아무 행사도 없는 2023년 12월 행사를 조회하면, 빈 목록을 반환한다.") void findEvents_2023_12() { // given, when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, null); - + // then assertThat(actualEvents).isEmpty(); } - - + + @Test @DisplayName("아무 행사도 없는 2023년 12월의 행사를 tag로 필터링하면, 빈 목록을 반환한다.") void findEvents_empty_tag_filter() { @@ -342,11 +342,11 @@ void findEvents_empty_tag_filter() { final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", List.of("안드로이드"), null); - + // then assertThat(actualEvents).isEmpty(); } - + @Test @DisplayName("아무 행사도 없는 2023년 12월의 행사를 status로 필터링하면, 빈 목록을 반환한다.") void findEvents_empty_status_filter() { @@ -354,11 +354,11 @@ void findEvents_empty_status_filter() { final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-12-01", "2023-12-31", null, List.of(IN_PROGRESS)); - + // then assertThat(actualEvents).isEmpty(); } - + @ParameterizedTest @ValueSource(strings = {"abcde", "00-0-0", "-1-1-1-1", "2023-02-30"}) @DisplayName("유효하지 않은 값이 시작일 정보로 들어오면 예외를 반환한다.") @@ -366,13 +366,13 @@ void findEvents_start_date_fail(final String startDate) { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, startDate, "2023-07-31", null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(INVALID_DATE_FORMAT.errorMessage()); } - + @ParameterizedTest @ValueSource(strings = {"abcde", "00-0-0", "-1-1-1-1", "2023-02-30"}) @DisplayName("유효하지 않은 값이 종료일 값으로 들어오면 예외를 반환한다.") @@ -380,122 +380,122 @@ void findEvents_end_date_fail(final String endDate) { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-01", endDate, null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(INVALID_DATE_FORMAT.errorMessage()); } - + @Test @DisplayName("시작일이 종료일보다 뒤에 있으면 예외를 반환한다.") void findEvents_start_after_end_fail() { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-07-16", "2023-07-15", null, null); - + // then assertThatThrownBy(actual) .isInstanceOf(EventException.class) .hasMessage(START_DATE_AFTER_END_DATE.errorMessage()); } - + @Test @DisplayName("'안드로이드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_tag_filter() { // given final List expectedEvents = List.of(인프콘_2023, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("안드로이드"), null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("'안드로이드', '백엔드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_tags_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, 모바일_컨퍼런스, 안드로이드_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("안드로이드", "백엔드"), null); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("존재하지 않는 태그가 입력으로 들어오면 예외를 반환한다.") void findEvents_tag_filter_fail() { // given, when final ThrowingCallable actual = () -> eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, List.of("개발"), null); - + // then assertThatThrownBy(actual) .isInstanceOf(TagException.class) .hasMessage(TagExceptionType.NOT_FOUND_TAG.errorMessage()); } - + @Test @DisplayName("'진행 중' 상태의 행사 목록을 조회할 수 있다.") void findEvents_status_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, List.of(IN_PROGRESS)); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("'진행 중' 및 '진행 예정' 상태의 행사 목록을 조회할 수 있다.") void findEvents_statuses_filter() { // given final List expectedEvents = List.of(인프콘_2023, 웹_컨퍼런스, AI_컨퍼런스, 모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, null, null, null, List.of(EventStatus.UPCOMING, IN_PROGRESS)); - + // then assertThat(actualEvents) .usingRecursiveComparison() .comparingOnlyFields("name", "status", "applyStatus") .isEqualTo(expectedEvents); } - + @Test @DisplayName("9월에 존재하는 진행 예정인 '안드로이드', '백엔드' 태그를 포함하는 행사 목록을 조회할 수 있다.") void findEvents_period_tags_filter() { // given final List expectedEvents = List.of(모바일_컨퍼런스); - + // when final List actualEvents = eventService.findEvents(EventType.CONFERENCE, TODAY, "2023-09-01", "2023-09-30", List.of("안드로이드", "백엔드"), List.of(EventStatus.UPCOMING)); - + // then assertThat(actualEvents) .usingRecursiveComparison() @@ -503,10 +503,10 @@ void findEvents_period_tags_filter() { .isEqualTo(expectedEvents); } } - + @Nested class AddEvent { - + final List tagRequests = List.of( new TagRequest(IOS().getName()), new TagRequest(AI().getName()) @@ -522,14 +522,14 @@ class AddEvent { private final EventType type = EventType.CONFERENCE; private final LocalDate now = LocalDate.now(); private final String organization = "행사기관"; - + @Test @DisplayName("이벤트를 성공적으로 저장한다.") void addEventTest() { //given final Image image1 = new Image("image", ImageType.EVENT, 1L, 0, LocalDateTime.now()); final Image image2 = new Image("image", ImageType.EVENT, 1L, 0, LocalDateTime.now()); - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -545,13 +545,13 @@ void addEventTest() { paymentType, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when final EventDetailResponse response = eventService.addEvent(request, mockMultipartFiles, now); final Event savedEvent = eventRepository.findById(response.getId()).get(); - + //then assertAll( () -> assertEquals(eventName, savedEvent.getName()), @@ -567,14 +567,14 @@ void addEventTest() { .collect(Collectors.toList()) ); } - + @Test @DisplayName("행사 시작 일시가 행사 종료 일시 이후일 경우 EventException이 발생한다.") void addEventWithStartDateTimeAfterBeforeDateTimeTest() { //given final LocalDateTime startDateTime = afterDateTime; final LocalDateTime endDatetime = beforeDateTime; - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -590,16 +590,16 @@ void addEventWithStartDateTimeAfterBeforeDateTimeTest() { paymentType, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.addEvent(request, mockMultipartFiles, now)); - + assertEquals(exception.exceptionType(), START_DATE_TIME_AFTER_END_DATE_TIME); } - + @Test @DisplayName("Tag가 존재하지 않을 경우 EventException이 발생한다.") void addEventWithNotExistTagTest() { @@ -609,7 +609,7 @@ void addEventWithNotExistTagTest() { new TagRequest(안드로이드().getName()), new TagRequest("존재하지 않는 태그") ); - + final EventDetailRequest request = new EventDetailRequest( eventName, eventLocation, @@ -625,20 +625,20 @@ void addEventWithNotExistTagTest() { paymentType, organization ); - + doNothing().when(firebaseCloudMessageClient).sendMessageTo(any(UpdateNotification.class)); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.addEvent(request, mockMultipartFiles, now)); - + assertEquals(exception.exceptionType(), NOT_FOUND_TAG); } } - + @Nested class UpdateEvent { - + final List newTagRequests = List.of( new TagRequest(IOS().getName()), new TagRequest(AI().getName()) @@ -653,14 +653,14 @@ class UpdateEvent { private final PaymentType paymentType = PaymentType.FREE_PAID; private final EventMode eventMode = EventMode.ON_OFFLINE; private final String organization = "행사기관"; - + @Test @DisplayName("이벤트를 성공적으로 업데이트한다.") void updateEventTest() { //given final LocalDateTime newStartDateTime = beforeDateTime; final LocalDateTime newEndDateTime = afterDateTime; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -676,15 +676,15 @@ void updateEventTest() { paymentType, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when final EventDetailResponse response = eventService.updateEvent(eventId, updateRequest, mockMultipartFiles, now); final Event updatedEvent = eventRepository.findById(eventId).get(); - + //then assertAll( () -> assertEquals(newName, updatedEvent.getName()), @@ -700,13 +700,13 @@ void updateEventTest() { .collect(Collectors.toList()) ); } - + @Test @DisplayName("업데이트할 이벤트가 존재하지 않을 경우 EventException이 발생한다.") void updateEventWithNotExistsEventTest() { //given final long notExistsEventId = 0L; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -722,21 +722,21 @@ void updateEventWithNotExistsEventTest() { paymentType, organization ); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.updateEvent(notExistsEventId, updateRequest, mockMultipartFiles, now)); - + assertEquals(exception.exceptionType(), NOT_FOUND_EVENT); } - + @Test @DisplayName("행사 시작 일시가 행사 종료 일시 이후일 경우 EventException이 발생한다.") void updateEventWithStartDateTimeAfterBeforeDateTimeTest() { //given final LocalDateTime newStartDateTime = afterDateTime; final LocalDateTime newEndDateTime = beforeDateTime; - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -752,17 +752,17 @@ void updateEventWithStartDateTimeAfterBeforeDateTimeTest() { paymentType, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.updateEvent(eventId, updateRequest, mockMultipartFiles, now)); - + assertEquals(exception.exceptionType(), START_DATE_TIME_AFTER_END_DATE_TIME); } - + @Test @DisplayName("Tag가 존재하지 않을 경우 EventException이 발생한다.") void updateEventWithNotExistTagTest() { @@ -770,7 +770,7 @@ void updateEventWithNotExistTagTest() { final List newTagRequests = List.of( new TagRequest("존재하지 않는 태그") ); - + final EventDetailRequest updateRequest = new EventDetailRequest( newName, newLocation, @@ -786,45 +786,45 @@ void updateEventWithNotExistTagTest() { paymentType, organization ); - + final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.updateEvent(eventId, updateRequest, mockMultipartFiles, now)); - + assertEquals(exception.exceptionType(), NOT_FOUND_TAG); } } - + @Nested class DeleteEvent { - + @Test @DisplayName("이벤트를 성공적으로 삭제한다.") void deleteEventTest() { //given final Event event = eventRepository.save(인프콘_2023()); final Long eventId = event.getId(); - + //when eventService.deleteEvent(eventId); - + //then assertFalse(eventRepository.findById(eventId).isPresent()); } - + @Test @DisplayName("삭제할 이벤트가 존재하지 않을 경우 EventException이 발생한다.") void deleteEventWithNotExistsEventTest() { //given final long notExistsEventId = 0L; - + //when & then final EventException exception = assertThrowsExactly(EventException.class, () -> eventService.deleteEvent(notExistsEventId)); - + assertEquals(exception.exceptionType(), NOT_FOUND_EVENT); } } diff --git a/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java b/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java index 436e6ae93..39f0e5d0a 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/image/application/ImageCommandServiceTest.java @@ -38,7 +38,7 @@ import org.springframework.web.multipart.MultipartFile; class ImageCommandServiceTest extends ServiceIntegrationTestHelper { - + private ImageCommandService imageCommandService; private ImageCommandService imageCommandServiceWithMockImageRepository; @Autowired @@ -51,7 +51,7 @@ class ImageCommandServiceTest extends ServiceIntegrationTestHelper { @Autowired private MemberRepository memberRepository; private S3Client s3Client; - + @BeforeEach void setUp() { s3Client = mock(S3Client.class); @@ -69,11 +69,11 @@ void setUp() { feedRepository ); } - + @Nested @DisplayName("saveImages() 메서드를 호출하면 S3와 DB에 이미지를 업로드한다.") class SaveImages { - + @Test @DisplayName("S3와 DB에 Image를 성공적으로 업로드할 수 있다.") void saveImages_success() { @@ -86,14 +86,14 @@ void saveImages_success() { final List files = List.of( new MockMultipartFile("test", "test.png", "", new byte[]{}), new MockMultipartFile("test", "test.png", "", new byte[]{})); - + BDDMockito.given(s3Client.uploadImages(any())) .willReturn(imageNames); - + //when imageCommandService.saveImages(ImageType.EVENT, event.getId(), files); final List actual = imageRepository.findAll(); - + //then assertAll( () -> assertThat(actual) @@ -104,7 +104,7 @@ void saveImages_success() { .uploadImages(any()) ); } - + @Test @DisplayName("이미지를 추가하려는 행사가 존재하지 않는 행사인 경우 예외를 던진다.") void saveImages_fail_not_found_event() { @@ -113,16 +113,16 @@ void saveImages_fail_not_found_event() { final List files = List.of( new MockMultipartFile("test", "test.png", "", new byte[]{}), new MockMultipartFile("test", "test.png", "", new byte[]{})); - + //when final ThrowingCallable actual = () -> imageCommandService.saveImages(ImageType.EVENT, noExistEventId, files); - + //then assertThatThrownBy(actual).isInstanceOf(EventException.class) .hasMessage(EventExceptionType.NOT_FOUND_EVENT.errorMessage()); } - + @Test @DisplayName("이미지를 추가하려는 행사가 존재하지 않는 피드인 경우 예외를 던진다.") void saveImages_fail_not_found_feed() { @@ -131,16 +131,16 @@ void saveImages_fail_not_found_feed() { final List files = List.of( new MockMultipartFile("test", "test.png", "", new byte[]{}), new MockMultipartFile("test", "test.png", "", new byte[]{})); - + //when final ThrowingCallable actual = () -> imageCommandService.saveImages(ImageType.FEED, noExistFeedId, files); - + //then assertThatThrownBy(actual).isInstanceOf(FeedException.class) .hasMessage(FeedExceptionType.NOT_FOUND_FEED.errorMessage()); } - + @Test @DisplayName("추가하려는 이미지의 개수가 컨텐츠의 최대 이미지 개수보다 크면 예외를 던진다.") void saveImages_fail_over_max_image_count() { @@ -155,16 +155,16 @@ void saveImages_fail_over_max_image_count() { new MockMultipartFile("test", "test4.png", "", new byte[]{}), new MockMultipartFile("test", "test5.png", "", new byte[]{}), new MockMultipartFile("test", "test6.png", "", new byte[]{})); - + //when final ThrowingCallable actual = () -> imageCommandService.saveImages(ImageType.FEED, feed.getId(), files); - + //then assertThatThrownBy(actual).isInstanceOf(ImageException.class) .hasMessage(ImageExceptionType.OVER_MAX_IMAGE_COUNT.errorMessage()); } - + @Test @DisplayName("이미지를 DB에 저장하는 작업이 실패하면 S3에 저장된 이미지를 삭제하고 예외를 던진다.") void saveImages_fail_and_rollback() { @@ -174,17 +174,17 @@ void saveImages_fail_and_rollback() { final List files = List.of( new MockMultipartFile("test", "test.png", "", new byte[]{}), new MockMultipartFile("test", "test.png", "", new byte[]{})); - + BDDMockito.given(s3Client.uploadImages(any())) .willReturn(imageNames); BDDMockito.willDoNothing().given(s3Client).deleteImages(any()); BDDMockito.given(mockImageRepository.save(any(Image.class))) .willThrow(new IllegalArgumentException()); - + //when final ThrowingCallable actual = () -> imageCommandServiceWithMockImageRepository.saveImages( ImageType.EVENT, event.getId(), files); - + //then assertThatThrownBy(actual) .isInstanceOf(ImageException.class) @@ -197,11 +197,11 @@ void saveImages_fail_and_rollback() { ); } } - + @Nested @DisplayName("deleteImages() 메서드를 호출하면 특정 컨텐츠의 이미지들을 S3와 DB에서 삭제한다.") class DeleteImages { - + @Test @DisplayName("S3와 DB에서 Image를 성공적으로 삭제할 수 있다.") void deleteImages_success() { @@ -211,13 +211,13 @@ void deleteImages_success() { new Image("테스트테스트.png", ImageType.EVENT, event.getId(), 0, null), new Image("테스트테스트2.png", ImageType.EVENT, event.getId(), 1, null)); imageRepository.saveAll(expected); - + BDDMockito.willDoNothing().given(s3Client).deleteImages(any()); - + //when imageCommandService.deleteImages(ImageType.EVENT, event.getId()); final List actual = imageRepository.findAll(); - + //then assertAll( () -> assertThat(actual).isEmpty(), diff --git a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java index e73484cca..72679be29 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java @@ -7,14 +7,14 @@ import org.junit.jupiter.params.provider.CsvSource; class ImageTypeTest { - + @ParameterizedTest @CsvSource(value = {"FEED:3:false", "FEED:6:true"}, delimiter = ':') @DisplayName("isOverMaxImageCount(): 입력받은 값이 이미지 유형의 최대 이미지 수보다 큰지 여부를 반환한다.") void isOverMaxImageCount(final ImageType type, final int imageCount, final boolean expected) { //given, when final boolean actual = type.isOverMaxImageCount(imageCount); - + //then assertThat(actual).isEqualTo(expected); } From fc31ef3be1900991100c83c92a695d0cccd506b2 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Mon, 25 Sep 2023 15:51:56 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=83=81=ED=95=9C=EC=9D=B4=20=EC=97=86=EB=8A=94=20=EC=BB=A8?= =?UTF-8?q?=ED=85=90=EC=B8=A0=EC=9D=98=20maxImageCount=20=EA=B0=92?= =?UTF-8?q?=EC=9D=84=200=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #633 --- .../src/main/java/com/emmsale/image/domain/ImageType.java | 6 +++++- .../test/java/com/emmsale/image/domain/ImageTypeTest.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java index 00f5380cd..8e0f876f3 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java +++ b/backend/emm-sale/src/main/java/com/emmsale/image/domain/ImageType.java @@ -2,8 +2,9 @@ public enum ImageType { FEED(5), - EVENT(200); + EVENT(0); + private static final int NO_LIMIT_COUNT = 0; private final int maxImageCount; ImageType(final int maxImageCount) { @@ -11,6 +12,9 @@ public enum ImageType { } public boolean isOverMaxImageCount(final int imageCount) { + if (maxImageCount == NO_LIMIT_COUNT) { + return false; + } return imageCount > maxImageCount; } diff --git a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java index 72679be29..6d36af768 100644 --- a/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java +++ b/backend/emm-sale/src/test/java/com/emmsale/image/domain/ImageTypeTest.java @@ -9,7 +9,7 @@ class ImageTypeTest { @ParameterizedTest - @CsvSource(value = {"FEED:3:false", "FEED:6:true"}, delimiter = ':') + @CsvSource(value = {"FEED:3:false", "FEED:6:true", "EVENT:999:false"}, delimiter = ':') @DisplayName("isOverMaxImageCount(): 입력받은 값이 이미지 유형의 최대 이미지 수보다 큰지 여부를 반환한다.") void isOverMaxImageCount(final ImageType type, final int imageCount, final boolean expected) { //given, when From 6cdbf699f5a0604f2fefb2106a53e2632f877883 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Mon, 25 Sep 2023 16:00:15 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor:=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 줄바꿈 여백 제거 #633 --- .../java/com/emmsale/event/api/EventApi.java | 12 ++-- .../event/application/EventService.java | 66 +++++++++---------- .../application/dto/EventDetailRequest.java | 2 +- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java index 4eaeb2f33..30306877e 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java @@ -29,14 +29,14 @@ @RequestMapping("/events") @RequiredArgsConstructor public class EventApi { - + private final EventService eventService; - + @GetMapping("/{id}") public ResponseEntity findEventById(@PathVariable final Long id) { return ResponseEntity.ok(eventService.findEvent(id, LocalDate.now())); } - + @GetMapping public ResponseEntity> findEvents( @RequestParam final EventType category, @@ -47,14 +47,14 @@ public ResponseEntity> findEvents( return ResponseEntity.ok( eventService.findEvents(category, LocalDate.now(), startDate, endDate, tags, statuses)); } - + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.CREATED) public EventDetailResponse addEvent(@RequestPart @Valid final EventDetailRequest request, @RequestPart final List images) { return eventService.addEvent(request, images, LocalDate.now()); } - + @PutMapping("/{eventId}") @ResponseStatus(HttpStatus.OK) public EventDetailResponse updateEvent(@PathVariable final Long eventId, @@ -62,7 +62,7 @@ public EventDetailResponse updateEvent(@PathVariable final Long eventId, @RequestPart final List images) { return eventService.updateEvent(eventId, request, images, LocalDate.now()); } - + @DeleteMapping("/{eventId}") @ResponseStatus(HttpStatus.NO_CONTENT) public void deleteEvent(@PathVariable final Long eventId) { diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java b/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java index 70b1eb213..702f5d586 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/application/EventService.java @@ -44,43 +44,43 @@ @Transactional @RequiredArgsConstructor public class EventService { - + private static final String MIN_DATE = "2000-01-01"; private static final String MAX_DATE = "2999-12-31"; - + private final EventRepository eventRepository; private final EventTagRepository eventTagRepository; private final TagRepository tagRepository; private final EventPublisher eventPublisher; private final ImageCommandService imageCommandService; private final ImageRepository imageRepository; - + @Transactional(readOnly = true) public EventDetailResponse findEvent(final Long id, final LocalDate today) { final Event event = eventRepository.findById(id) .orElseThrow(() -> new EventException(NOT_FOUND_EVENT)); - + final List imageUrls = imageRepository .findAllByTypeAndContentId(ImageType.EVENT, event.getId()) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + return EventDetailResponse.from(event, today, imageUrls); } - + @Transactional(readOnly = true) public List findEvents(final EventType category, final LocalDate nowDate, final String startDate, final String endDate, final List tagNames, final List statuses) { Specification spec = Specification.where(filterByCategory(category)); - + if (isExistTagNames(tagNames)) { validateTags(tagNames); spec = spec.and(filterByTags(tagNames)); } - + if (isExistFilterDate(startDate, endDate)) { final LocalDateTime startDateTime = validateStartDate(startDate); final LocalDateTime endDateTime = validateEndDate(endDate); @@ -90,25 +90,25 @@ public List findEvents(final EventType category, final List events = eventRepository.findAll(spec); final EnumMap> eventsForEventStatus = groupByEventStatus(nowDate, events); - + return filterByStatuses(nowDate, statuses, eventsForEventStatus); } - + private boolean isExistTagNames(final List tagNames) { return tagNames != null; } - + private void validateTags(final List tagNames) { final List tags = tagRepository.findByNameIn(tagNames); if (tags.size() != tagNames.size()) { throw new TagException(NOT_FOUND_TAG); } } - + private boolean isExistFilterDate(final String startDate, final String endDate) { return startDate != null || endDate != null; } - + private LocalDateTime validateStartDate(final String date) { try { if (date == null) { @@ -119,7 +119,7 @@ private LocalDateTime validateStartDate(final String date) { throw new EventException(EventExceptionType.INVALID_DATE_FORMAT); } } - + private LocalDateTime validateEndDate(final String date) { try { if (date == null) { @@ -130,14 +130,14 @@ private LocalDateTime validateEndDate(final String date) { throw new EventException(EventExceptionType.INVALID_DATE_FORMAT); } } - + private void validateEndDateAfterDateStart(final LocalDateTime startDate, final LocalDateTime endDate) { if (endDate.isBefore(startDate)) { throw new EventException(EventExceptionType.START_DATE_AFTER_END_DATE); } } - + private EnumMap> groupByEventStatus(final LocalDate nowDate, final List events) { return events.stream() @@ -147,7 +147,7 @@ private EnumMap> groupByEventStatus(final LocalDate now () -> new EnumMap<>(EventStatus.class), toList()) ); } - + private List filterByStatuses( final LocalDate today, final List statuses, @@ -158,11 +158,11 @@ private List filterByStatuses( } return EventResponse.mergeEventResponses(today, eventsForEventStatus); } - + private boolean isExistStatusName(final List statuses) { return statuses != null; } - + private List filterEventResponseByStatuses( final LocalDate today, final List statuses, @@ -178,34 +178,34 @@ private List filterEventResponseByStatuses( return combinedEvents; }); } - + public EventDetailResponse addEvent(final EventDetailRequest request, final List images, final LocalDate today) { final Event event = eventRepository.save(request.toEvent()); final List tags = findAllPersistTagsOrElseThrow(request.getTags()); event.addAllEventTags(tags); - + final List imageUrls = imageCommandService .saveImages(ImageType.EVENT, event.getId(), images) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + eventPublisher.publish(event); - + return EventDetailResponse.from(event, today, imageUrls); } - + public EventDetailResponse updateEvent(final Long eventId, final EventDetailRequest request, final List images, final LocalDate today) { final Event event = eventRepository.findById(eventId) .orElseThrow(() -> new EventException(NOT_FOUND_EVENT)); - + final List tags = findAllPersistTagsOrElseThrow(request.getTags()); - + eventTagRepository.deleteAllByEventId(eventId); - + final Event updatedEvent = event.updateEventContent( request.getName(), request.getLocation(), @@ -216,30 +216,30 @@ public EventDetailResponse updateEvent(final Long eventId, final EventDetailRequ request.getInformationUrl(), tags ); - + final List imageUrls = imageRepository .findAllByTypeAndContentId(ImageType.EVENT, event.getId()) .stream() .sorted(comparing(Image::getOrder)) .map(Image::getName) .collect(toList()); - + return EventDetailResponse.from(updatedEvent, today, imageUrls); } - + public void deleteEvent(final Long eventId) { if (!eventRepository.existsById(eventId)) { throw new EventException(NOT_FOUND_EVENT); } - + eventRepository.deleteById(eventId); } - + private List findAllPersistTagsOrElseThrow(final List tags) { if (tags == null || tags.isEmpty()) { return new ArrayList<>(); } - + return tags.stream() .map(tag -> tagRepository.findByName(tag.getName()) .orElseThrow(() -> new EventException(EventExceptionType.NOT_FOUND_TAG))) diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java index 722d5c044..e82c8fcfe 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/application/dto/EventDetailRequest.java @@ -51,7 +51,7 @@ public class EventDetailRequest { private final PaymentType paymentType; private final String organization; - + public Event toEvent() { return new Event( name, From 7d16992734368ba3b1d8e69f9ada3190b4d6a814 Mon Sep 17 00:00:00 2001 From: amaran-th Date: Mon, 25 Sep 2023 16:58:00 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=20API?= =?UTF-8?q?=EC=9D=98=20consumes=20=EC=86=8D=EC=84=B1=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #633 --- .../emm-sale/src/main/java/com/emmsale/event/api/EventApi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java index 30306877e..c95630d3e 100644 --- a/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java +++ b/backend/emm-sale/src/main/java/com/emmsale/event/api/EventApi.java @@ -55,7 +55,7 @@ public EventDetailResponse addEvent(@RequestPart @Valid final EventDetailRequest return eventService.addEvent(request, images, LocalDate.now()); } - @PutMapping("/{eventId}") + @PutMapping(path = "/{eventId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ResponseStatus(HttpStatus.OK) public EventDetailResponse updateEvent(@PathVariable final Long eventId, @RequestPart @Valid final EventDetailRequest request,