Skip to content

Commit

Permalink
✨ feat(api): get announcement (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
siyeonSon authored Jun 13, 2024
1 parent 46bceef commit 1c2450a
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package com.depromeet.common.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class PageMetaResponseDto implements MetaInterface {
private int page;
private int size;
Expand Down
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);
}

}
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()
);
}
}
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> {
}
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));
}

}
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());
}
}
}

}
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);
}
}
}

}

0 comments on commit 1c2450a

Please sign in to comment.