-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ feat(api): get announcement (#476)
- Loading branch information
Showing
7 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
backend/streetdrop-api/src/main/java/com/depromeet/common/dto/PageMetaResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
...i/src/main/java/com/depromeet/domains/announcement/controller/AnnouncementController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.depromeet.domains.announcement.controller; | ||
|
||
import com.depromeet.common.dto.PaginationResponseDto; | ||
import com.depromeet.common.dto.ResponseDto; | ||
import com.depromeet.domains.announcement.dto.response.AnnouncementResponseDto; | ||
import com.depromeet.domains.announcement.service.AnnouncementService; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@RestController | ||
@RequestMapping("/announcements") | ||
@RequiredArgsConstructor | ||
@Tag(name = "🔈Announcement", description = "Announcement API") | ||
public class AnnouncementController { | ||
|
||
private final AnnouncementService announcementService; | ||
|
||
@Operation(summary = "공지사항 전체 조회") | ||
@GetMapping | ||
public ResponseEntity<PaginationResponseDto<?, ?>> getAnnouncements() { | ||
var response = announcementService.getAnnouncements(); | ||
return ResponseDto.ok(response); | ||
} | ||
|
||
@Operation(summary = "공지사항 단건 조회") | ||
@GetMapping("{announcementId}") | ||
public ResponseEntity<AnnouncementResponseDto> getAnnouncement( | ||
@PathVariable(value = "announcementId") Long announcementId | ||
) { | ||
var response = announcementService.getAnnouncement(announcementId); | ||
return ResponseDto.ok(response); | ||
} | ||
|
||
} |
28 changes: 28 additions & 0 deletions
28
...rc/main/java/com/depromeet/domains/announcement/dto/response/AnnouncementResponseDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.depromeet.domains.announcement.dto.response; | ||
|
||
import com.depromeet.announcement.Announcement; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.time.LocalDateTime; | ||
|
||
public record AnnouncementResponseDto( | ||
@Schema(description = "공지사항 아이디", example = "1") | ||
Long announcementId, | ||
|
||
@Schema(description = "공지사항 제목", example = "사용성 개선") | ||
String title, | ||
|
||
@Schema(description = "공지사항 내용", example = "숨어있던 버그들이 해결되었어요") | ||
String content, | ||
|
||
@Schema(description = "생성시간", example = "yyyy-MM-dd HH:mm:ss") | ||
LocalDateTime createdAt | ||
) { | ||
public AnnouncementResponseDto(Announcement announcement) { | ||
this( | ||
announcement.getId(), | ||
announcement.getTitle(), | ||
announcement.getContent(), | ||
announcement.getCreatedAt() | ||
); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
...i/src/main/java/com/depromeet/domains/announcement/repository/AnnouncementRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.depromeet.domains.announcement.repository; | ||
|
||
import com.depromeet.announcement.Announcement; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
public interface AnnouncementRepository extends JpaRepository<Announcement, Long> { | ||
} |
43 changes: 43 additions & 0 deletions
43
...rop-api/src/main/java/com/depromeet/domains/announcement/service/AnnouncementService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.depromeet.domains.announcement.service; | ||
|
||
import com.depromeet.common.dto.MetaInterface; | ||
import com.depromeet.common.dto.PageMetaResponseDto; | ||
import com.depromeet.common.dto.PaginationResponseDto; | ||
import com.depromeet.common.error.dto.CommonErrorCode; | ||
import com.depromeet.common.error.exception.internal.NotFoundException; | ||
import com.depromeet.domains.announcement.dto.response.AnnouncementResponseDto; | ||
import com.depromeet.domains.announcement.repository.AnnouncementRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class AnnouncementService { | ||
|
||
private final AnnouncementRepository announcementRepository; | ||
|
||
@Transactional(readOnly = true) | ||
public PaginationResponseDto<AnnouncementResponseDto, MetaInterface> getAnnouncements() { | ||
var announcements = announcementRepository.findAll() | ||
.stream() | ||
.map(AnnouncementResponseDto::new) | ||
.toList(); | ||
var meta = PageMetaResponseDto.builder() | ||
.page(1) | ||
.size(announcements.size()) | ||
.totalPage(1) | ||
.firstPage(true) | ||
.lastPage(true) | ||
.build(); | ||
return new PaginationResponseDto<>(announcements, meta); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AnnouncementResponseDto getAnnouncement(Long announcementId) { | ||
return announcementRepository.findById(announcementId) | ||
.map(AnnouncementResponseDto::new) | ||
.orElseThrow(() -> new NotFoundException(CommonErrorCode.NOT_FOUND, announcementId)); | ||
} | ||
|
||
} |
137 changes: 137 additions & 0 deletions
137
...op-api/src/test/java/unit/domains/announcement/controller/AnnouncementControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package unit.domains.announcement.controller; | ||
|
||
import static org.mockito.Mockito.when; | ||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
import com.depromeet.announcement.Announcement; | ||
import com.depromeet.common.dto.MetaInterface; | ||
import com.depromeet.common.dto.PageMetaResponseDto; | ||
import com.depromeet.common.dto.PaginationResponseDto; | ||
import com.depromeet.common.error.GlobalExceptionHandler; | ||
import com.depromeet.common.error.dto.CommonErrorCode; | ||
import com.depromeet.common.error.exception.internal.NotFoundException; | ||
import com.depromeet.domains.announcement.controller.AnnouncementController; | ||
import com.depromeet.domains.announcement.dto.response.AnnouncementResponseDto; | ||
import com.depromeet.domains.announcement.service.AnnouncementService; | ||
import java.util.List; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.context.annotation.Import; | ||
import org.springframework.test.context.ContextConfiguration; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
|
||
@ContextConfiguration(classes = AnnouncementController.class) | ||
@WebMvcTest(controllers = {AnnouncementController.class}, excludeAutoConfiguration = {SecurityAutoConfiguration.class}) | ||
@Import({AnnouncementController.class, GlobalExceptionHandler.class}) | ||
@DisplayName("[API][Controller] AnnouncementController 테스트") | ||
public class AnnouncementControllerTest { | ||
|
||
@Autowired | ||
MockMvc mvc; | ||
|
||
@MockBean | ||
AnnouncementService announcementService; | ||
|
||
@DisplayName("[GET] 공지사항 전체 조회") | ||
@Nested | ||
class GetAnnouncementsTest { | ||
@Nested | ||
@DisplayName("성공") | ||
class Success { | ||
@DisplayName("공지사항 0개 조회") | ||
@Test | ||
void getAnnouncementsTestSuccess1() throws Exception { | ||
var meta = PageMetaResponseDto.builder() | ||
.page(0) | ||
.size(0) | ||
.totalPage(0) | ||
.firstPage(true) | ||
.lastPage(true) | ||
.build(); | ||
PaginationResponseDto<AnnouncementResponseDto, MetaInterface> paginationResponseDto = new PaginationResponseDto<>(List.of(), meta); | ||
when(announcementService.getAnnouncements()).thenReturn(paginationResponseDto); | ||
|
||
var response = mvc.perform(get("/announcements")); | ||
response.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.data").isArray()) | ||
.andExpect(jsonPath("$.data.length()").value(0)) | ||
.andExpect(jsonPath("$.meta.page").value(0)) | ||
.andExpect(jsonPath("$.meta.size").value(0)) | ||
.andExpect(jsonPath("$.meta.totalPage").value(0)) | ||
.andExpect(jsonPath("$.meta.firstPage").value(true)) | ||
.andExpect(jsonPath("$.meta.lastPage").value(true)); | ||
} | ||
|
||
@DisplayName("공지사항 2개 조회") | ||
@Test | ||
void getAnnouncementsTestSuccess2() throws Exception { | ||
var announcementResponseDto = List.of( | ||
new AnnouncementResponseDto(new Announcement("Title 1", "Content 1")), | ||
new AnnouncementResponseDto(new Announcement("Title 2", "Content 2")) | ||
); | ||
var meta = PageMetaResponseDto.builder() | ||
.page(0) | ||
.size(0) | ||
.totalPage(0) | ||
.firstPage(true) | ||
.lastPage(true) | ||
.build(); | ||
PaginationResponseDto<AnnouncementResponseDto, MetaInterface> paginationResponseDto = new PaginationResponseDto<>(announcementResponseDto, meta); | ||
when(announcementService.getAnnouncements()).thenReturn(paginationResponseDto); | ||
|
||
var response = mvc.perform(get("/announcements")); | ||
response.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.data").isArray()) | ||
.andExpect(jsonPath("$.data.length()").value(2)) | ||
.andExpect(jsonPath("$.data[0].title").value("Title 1")) | ||
.andExpect(jsonPath("$.data[0].content").value("Content 1")) | ||
.andExpect(jsonPath("$.data[1].title").value("Title 2")) | ||
.andExpect(jsonPath("$.data[1].content").value("Content 2")); | ||
} | ||
} | ||
} | ||
|
||
@DisplayName("[GET] 공지사항 단건 조회") | ||
@Nested | ||
class GetAnnouncementTest { | ||
@Nested | ||
@DisplayName("성공") | ||
class Success { | ||
@DisplayName("id가 일치하는 경우") | ||
@Test | ||
void getAnnouncementTestSuccess1() throws Exception { | ||
var announcementId = 1L; | ||
var announcementResponseDto = new AnnouncementResponseDto(new Announcement("Title 1", "Content 1")); | ||
when(announcementService.getAnnouncement(announcementId)).thenReturn(announcementResponseDto); | ||
|
||
var response = mvc.perform(get("/announcements/{announcementId}", announcementId)); | ||
response.andExpect(status().isOk()) | ||
.andExpect(jsonPath("$.title").value("Title 1")) | ||
.andExpect(jsonPath("$.content").value("Content 1")); | ||
} | ||
} | ||
|
||
@Nested | ||
@DisplayName("실패") | ||
class Fail { | ||
@DisplayName("id가 일치하지 않는 경우") | ||
@Test | ||
void getAnnouncementTestFail1() throws Exception { | ||
var announcementId = 1L; | ||
when(announcementService.getAnnouncement(announcementId)) | ||
.thenThrow(new NotFoundException(CommonErrorCode.NOT_FOUND, announcementId)); | ||
|
||
var response = mvc.perform(get("/announcements/{announcementId}", announcementId)); | ||
response.andExpect(status().isNotFound()); | ||
} | ||
} | ||
} | ||
|
||
} |
106 changes: 106 additions & 0 deletions
106
...reetdrop-api/src/test/java/unit/domains/announcement/service/AnnouncementServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package unit.domains.announcement.service; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.depromeet.announcement.Announcement; | ||
import com.depromeet.common.error.exception.internal.NotFoundException; | ||
import com.depromeet.domains.announcement.dto.response.AnnouncementResponseDto; | ||
import com.depromeet.domains.announcement.repository.AnnouncementRepository; | ||
import com.depromeet.domains.announcement.service.AnnouncementService; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Nested; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.junit.jupiter.MockitoExtension; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@DisplayName("[Service] AnnouncementService 테스트") | ||
class AnnouncementServiceTest { | ||
|
||
@InjectMocks | ||
AnnouncementService announcementService; | ||
|
||
@Mock | ||
AnnouncementRepository announcementRepository; | ||
|
||
@DisplayName("공지사항 전체 조회") | ||
@Nested | ||
class GetAnnouncementsTest { | ||
@Nested | ||
@DisplayName("성공") | ||
class Success { | ||
@DisplayName("공지사항 0개 조회") | ||
@Test | ||
void getAnnouncementsTestSuccess1() { | ||
when(announcementRepository.findAll()).thenReturn(List.of()); | ||
|
||
var result = announcementService.getAnnouncements(); | ||
|
||
assertThat(result.getData()).isEmpty(); | ||
} | ||
|
||
@DisplayName("공지사항 2개 조회") | ||
@Test | ||
void getAnnouncementsTestSuccess2() { | ||
List<Announcement> announcements = List.of( | ||
new Announcement("Title 1", "Content 1"), | ||
new Announcement("Title 2", "Content 2") | ||
); | ||
when(announcementRepository.findAll()).thenReturn(announcements); | ||
|
||
var result = announcementService.getAnnouncements(); | ||
|
||
assertThat(result.getData()).isEqualTo( | ||
List.of( | ||
new AnnouncementResponseDto(announcements.get(0)), | ||
new AnnouncementResponseDto(announcements.get(1)) | ||
) | ||
); | ||
} | ||
} | ||
} | ||
|
||
@DisplayName("공지사항 단건 조회") | ||
@Nested | ||
class GetAnnouncementTest { | ||
@Nested | ||
@DisplayName("성공") | ||
class Success { | ||
@DisplayName("id가 일치하는 경우") | ||
@Test | ||
void getAnnouncementTestSuccess1() { | ||
var announcement = new Announcement("Title 1", "Content 1"); | ||
when(announcementRepository.findById(1L)).thenReturn(Optional.of(announcement)); | ||
|
||
var result = announcementService.getAnnouncement(1L); | ||
|
||
assertThat(result).isEqualTo( | ||
new AnnouncementResponseDto(announcement) | ||
); | ||
} | ||
} | ||
|
||
@Nested | ||
@DisplayName("실패") | ||
class Fail { | ||
@DisplayName("id가 일치하지 않는 경우") | ||
@Test | ||
void getAnnouncementTestFail1() { | ||
var announcementId = 1L; | ||
|
||
when(announcementRepository.findById(announcementId)).thenReturn(Optional.empty()); | ||
when(announcementRepository.findById(1L)).thenReturn(Optional.empty()); | ||
|
||
assertThatThrownBy(() -> announcementService.getAnnouncement(announcementId)) | ||
.isInstanceOf(NotFoundException.class); | ||
} | ||
} | ||
} | ||
|
||
} |