diff --git a/backend/src/main/java/com/votogether/domain/report/controller/ReportCommandCommandController.java b/backend/src/main/java/com/votogether/domain/report/controller/ReportCommandController.java similarity index 91% rename from backend/src/main/java/com/votogether/domain/report/controller/ReportCommandCommandController.java rename to backend/src/main/java/com/votogether/domain/report/controller/ReportCommandController.java index 941ce0740..2cab9c34a 100644 --- a/backend/src/main/java/com/votogether/domain/report/controller/ReportCommandCommandController.java +++ b/backend/src/main/java/com/votogether/domain/report/controller/ReportCommandController.java @@ -13,7 +13,7 @@ @RequiredArgsConstructor @RestController -public class ReportCommandCommandController implements ReportCommandControllerDocs { +public class ReportCommandController implements ReportCommandControllerDocs { private final ReportCommandService reportCommandService; diff --git a/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryController.java b/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryController.java new file mode 100644 index 000000000..f92cdcd4e --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryController.java @@ -0,0 +1,28 @@ +package com.votogether.domain.report.controller; + +import com.votogether.domain.report.dto.response.ReportPageResponse; +import com.votogether.domain.report.service.ReportQueryService; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RequiredArgsConstructor +@RestController +public class ReportQueryController implements ReportQueryControllerDocs { + + private final ReportQueryService reportQueryService; + + @GetMapping("/reports/admin") + public ResponseEntity getReports( + @RequestParam @PositiveOrZero(message = "페이지는 0이상 정수만 가능합니다.") final int page + ) { + final ReportPageResponse reportPageResponse = reportQueryService.getReports(page); + return ResponseEntity.ok(reportPageResponse); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryControllerDocs.java b/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryControllerDocs.java new file mode 100644 index 000000000..2ec36a83c --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/controller/ReportQueryControllerDocs.java @@ -0,0 +1,33 @@ +package com.votogether.domain.report.controller; + +import com.votogether.domain.report.dto.response.ReportPageResponse; +import com.votogether.global.exception.ExceptionResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.PositiveOrZero; +import org.springframework.http.ResponseEntity; + +@Tag(name = "신고 조회", description = "신고 조회 API") +public interface ReportQueryControllerDocs { + + @Operation(summary = "신고 조치 예정 목록 조회", description = "신고 조치 예정 목록을 조회한다.") + @ApiResponses({ + @ApiResponse( + responseCode = "200", + description = "신고 조치 예정 목록 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "0이상의 정수가 아닌 페이지", + content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) + ) + }) + ResponseEntity getReports( + @PositiveOrZero(message = "페이지는 0이상 정수만 가능합니다.") final int page + ); + +} diff --git a/backend/src/main/java/com/votogether/domain/report/dto/ReportAggregateDto.java b/backend/src/main/java/com/votogether/domain/report/dto/ReportAggregateDto.java new file mode 100644 index 000000000..782e0f694 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/dto/ReportAggregateDto.java @@ -0,0 +1,13 @@ +package com.votogether.domain.report.dto; + +import com.votogether.domain.report.entity.vo.ReportType; +import java.time.LocalDateTime; + +public record ReportAggregateDto( + long reportMaxId, + ReportType reportType, + long targetId, + String reasons, + LocalDateTime createdAt +) { +} diff --git a/backend/src/main/java/com/votogether/domain/report/dto/response/ReportPageResponse.java b/backend/src/main/java/com/votogether/domain/report/dto/response/ReportPageResponse.java new file mode 100644 index 000000000..a9245b546 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/dto/response/ReportPageResponse.java @@ -0,0 +1,30 @@ +package com.votogether.domain.report.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; + +@Schema(description = "신고 조치 예정 목록 응답") +public record ReportPageResponse( + @Schema(description = "신고 조치 예정 목록 전체 페이지 수", example = "20") + long totalPageNumber, + + @Schema(description = "신고 조치 예정 목록 중 현재 페이지", example = "3") + long currentPageNumber, + + @Schema(description = "신고 조치 예정 목록") + List reports +) { + + public static ReportPageResponse of( + final int totalPageNumber, + final int currentPageNumber, + final List reportResponses + ) { + return new ReportPageResponse( + totalPageNumber, + currentPageNumber, + reportResponses + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/dto/response/ReportResponse.java b/backend/src/main/java/com/votogether/domain/report/dto/response/ReportResponse.java new file mode 100644 index 000000000..3f2d9d097 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/dto/response/ReportResponse.java @@ -0,0 +1,41 @@ +package com.votogether.domain.report.dto.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.votogether.domain.report.dto.ReportAggregateDto; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; + +@Schema(description = "신고 정보 응답") +public record ReportResponse( + @Schema(description = "신고 ID", example = "1") + long id, + + @Schema(description = "신고 유형", example = "POST") + ReportType type, + + @Schema(description = "신고 이유들") + List reasons, + + @Schema(description = "신고 당한 요소의 내용", example = "2") + String target, + + @Schema(description = "신고 생성시간", example = "2023-08-01 13:56") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm") + LocalDateTime createdAt +) { + + public static ReportResponse of(final ReportAggregateDto reportAggregateDto, final String target) { + return new ReportResponse( + reportAggregateDto.reportMaxId(), + reportAggregateDto.reportType(), + Arrays.stream(reportAggregateDto.reasons().split(",")).toList(), + target, + reportAggregateDto.createdAt() + ); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepository.java b/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepository.java new file mode 100644 index 000000000..1c9616ca6 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepository.java @@ -0,0 +1,11 @@ +package com.votogether.domain.report.repository; + +import com.votogether.domain.report.dto.ReportAggregateDto; +import java.util.List; +import org.springframework.data.domain.Pageable; + +public interface ReportCustomRepository { + + List findReportsGroupedByReportTypeAndTargetId(final Pageable pageable); + +} diff --git a/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepositoryImpl.java b/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepositoryImpl.java new file mode 100644 index 000000000..55c7f2ed4 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/repository/ReportCustomRepositoryImpl.java @@ -0,0 +1,40 @@ +package com.votogether.domain.report.repository; + +import static com.votogether.domain.report.entity.QReport.report; + +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.jpa.impl.JPAQueryFactory; +import com.votogether.domain.report.dto.ReportAggregateDto; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class ReportCustomRepositoryImpl implements ReportCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findReportsGroupedByReportTypeAndTargetId(final Pageable pageable) { + return jpaQueryFactory.select( + Projections.constructor( + ReportAggregateDto.class, + report.id.max(), + report.reportType, + report.targetId, + Expressions.stringTemplate("group_concat({0})", report.reason), + report.createdAt.max() + ) + ) + .from(report) + .orderBy(report.id.max().desc()) + .groupBy(report.reportType, report.targetId) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java b/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java index 025a88c59..82f0d7049 100644 --- a/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java +++ b/backend/src/main/java/com/votogether/domain/report/repository/ReportRepository.java @@ -10,7 +10,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -public interface ReportRepository extends JpaRepository { +public interface ReportRepository extends JpaRepository, ReportCustomRepository { int countByReportTypeAndTargetId(final ReportType reportType, final Long targetId); diff --git a/backend/src/main/java/com/votogether/domain/report/service/ReportCommandService.java b/backend/src/main/java/com/votogether/domain/report/service/ReportCommandService.java index c89e0d658..54169af0e 100644 --- a/backend/src/main/java/com/votogether/domain/report/service/ReportCommandService.java +++ b/backend/src/main/java/com/votogether/domain/report/service/ReportCommandService.java @@ -2,35 +2,21 @@ import com.votogether.domain.member.entity.Member; import com.votogether.domain.report.dto.request.ReportRequest; -import com.votogether.domain.report.entity.vo.ReportType; -import com.votogether.domain.report.service.strategy.ReportCommentStrategy; -import com.votogether.domain.report.service.strategy.ReportNicknameStrategy; -import com.votogether.domain.report.service.strategy.ReportPostStrategy; +import com.votogether.domain.report.service.strategy.ReportActionProvider; import com.votogether.domain.report.service.strategy.ReportStrategy; -import java.util.EnumMap; -import java.util.Map; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +@RequiredArgsConstructor @Transactional @Service public class ReportCommandService { - private final Map reportActions; - - public ReportCommandService( - final ReportPostStrategy reportPostStrategy, - final ReportCommentStrategy reportCommentStrategy, - final ReportNicknameStrategy reportNicknameStrategy - ) { - this.reportActions = new EnumMap<>(ReportType.class); - this.reportActions.put(ReportType.POST, reportPostStrategy); - this.reportActions.put(ReportType.COMMENT, reportCommentStrategy); - this.reportActions.put(ReportType.NICKNAME, reportNicknameStrategy); - } + private final ReportActionProvider reportActionProvider; public void report(final Member reporter, final ReportRequest request) { - final ReportStrategy reportStrategy = reportActions.get(request.type()); + final ReportStrategy reportStrategy = reportActionProvider.getStrategy(request.type()); reportStrategy.report(reporter, request); } diff --git a/backend/src/main/java/com/votogether/domain/report/service/ReportQueryService.java b/backend/src/main/java/com/votogether/domain/report/service/ReportQueryService.java new file mode 100644 index 000000000..8bee3e6bd --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/service/ReportQueryService.java @@ -0,0 +1,50 @@ +package com.votogether.domain.report.service; + +import com.votogether.domain.report.dto.ReportAggregateDto; +import com.votogether.domain.report.dto.response.ReportResponse; +import com.votogether.domain.report.dto.response.ReportPageResponse; +import com.votogether.domain.report.repository.ReportRepository; +import com.votogether.domain.report.service.strategy.ReportActionProvider; +import com.votogether.domain.report.service.strategy.ReportStrategy; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class ReportQueryService { + + private static final int BASIC_PAGE_SIZE = 20; + + private final ReportRepository reportRepository; + private final ReportActionProvider reportActionProvider; + + public ReportPageResponse getReports(final int page) { + final long totalCount = reportRepository.count(); + final int totalPageNumber = (int) Math.ceil((double) totalCount / BASIC_PAGE_SIZE); + + final Pageable pageable = PageRequest.of(page, BASIC_PAGE_SIZE); + final List reportAggregateDtos = reportRepository + .findReportsGroupedByReportTypeAndTargetId(pageable); + final List reportResponses = parseReportResponses(reportAggregateDtos); + + return ReportPageResponse.of(totalPageNumber, page, reportResponses); + } + + private List parseReportResponses(final List reportAggregateDtos) { + return reportAggregateDtos.stream() + .map(this::parseReportResponse) + .toList(); + } + + private ReportResponse parseReportResponse(final ReportAggregateDto reportAggregateDto) { + final ReportStrategy strategy = reportActionProvider.getStrategy(reportAggregateDto.reportType()); + return ReportResponse.of(reportAggregateDto, strategy.parseTarget(reportAggregateDto.targetId())); + } + +} + diff --git a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportActionProvider.java b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportActionProvider.java new file mode 100644 index 000000000..a45e23737 --- /dev/null +++ b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportActionProvider.java @@ -0,0 +1,28 @@ +package com.votogether.domain.report.service.strategy; + +import com.votogether.domain.report.entity.vo.ReportType; +import java.util.EnumMap; +import java.util.Map; +import org.springframework.stereotype.Component; + +@Component +public class ReportActionProvider { + + private final Map reportActions; + + public ReportActionProvider( + final ReportPostStrategy reportPostStrategy, + final ReportCommentStrategy reportCommentStrategy, + final ReportNicknameStrategy reportNicknameStrategy + ) { + this.reportActions = new EnumMap<>(ReportType.class); + this.reportActions.put(ReportType.POST, reportPostStrategy); + this.reportActions.put(ReportType.COMMENT, reportCommentStrategy); + this.reportActions.put(ReportType.NICKNAME, reportNicknameStrategy); + } + + public ReportStrategy getStrategy(ReportType type) { + return reportActions.get(type); + } + +} diff --git a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportCommentStrategy.java b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportCommentStrategy.java index 302f84626..8d0a79842 100644 --- a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportCommentStrategy.java +++ b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportCommentStrategy.java @@ -65,4 +65,11 @@ private void validateCommentMine(final Comment comment, final Member member) { } } + @Override + public String parseTarget(final Long targetId) { + final Comment reportedComment = commentRepository.findById(targetId) + .orElseThrow(() -> new NotFoundException(CommentExceptionType.NOT_FOUND)); + return reportedComment.getContent(); + } + } diff --git a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategy.java b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategy.java index c8714a42f..76d5fd6dd 100644 --- a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategy.java +++ b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategy.java @@ -56,4 +56,11 @@ private void changeNicknameByReport(final Member reportedMember, final ReportReq } } + @Override + public String parseTarget(final Long targetId) { + final Member reportedMember = memberRepository.findById(targetId) + .orElseThrow(() -> new NotFoundException(MemberExceptionType.NONEXISTENT_MEMBER)); + return reportedMember.getNickname(); + } + } diff --git a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportPostStrategy.java b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportPostStrategy.java index 5ebc55d23..5f5d4c342 100644 --- a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportPostStrategy.java +++ b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportPostStrategy.java @@ -65,4 +65,9 @@ private void validatePostMine(final Post post, final Member member) { } } + @Override + public String parseTarget(final Long targetId) { + return targetId.toString(); + } + } diff --git a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportStrategy.java b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportStrategy.java index 33b58a44e..b8a66f1f2 100644 --- a/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportStrategy.java +++ b/backend/src/main/java/com/votogether/domain/report/service/strategy/ReportStrategy.java @@ -7,11 +7,12 @@ import com.votogether.domain.report.repository.ReportRepository; import com.votogether.global.exception.BadRequestException; -@FunctionalInterface public interface ReportStrategy { void report(final Member reporter, final ReportRequest request); + String parseTarget(Long targetId); + default void validateDuplicatedReport( final Member reporter, final ReportRequest request, diff --git a/backend/src/test/java/com/votogether/domain/report/controller/ReportCommandControllerTest.java b/backend/src/test/java/com/votogether/domain/report/controller/ReportCommandControllerTest.java index 0534ccf64..aeb278f32 100644 --- a/backend/src/test/java/com/votogether/domain/report/controller/ReportCommandControllerTest.java +++ b/backend/src/test/java/com/votogether/domain/report/controller/ReportCommandControllerTest.java @@ -29,7 +29,7 @@ import org.springframework.http.HttpStatus; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest(ReportCommandCommandController.class) +@WebMvcTest(ReportCommandController.class) class ReportCommandControllerTest extends ControllerTest { @MockBean diff --git a/backend/src/test/java/com/votogether/domain/report/controller/ReportQueryControllerTest.java b/backend/src/test/java/com/votogether/domain/report/controller/ReportQueryControllerTest.java new file mode 100644 index 000000000..e68480a1e --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/controller/ReportQueryControllerTest.java @@ -0,0 +1,124 @@ +package com.votogether.domain.report.controller; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.BDDMockito.given; + +import com.votogether.domain.report.dto.ReportAggregateDto; +import com.votogether.domain.report.dto.response.ReportPageResponse; +import com.votogether.domain.report.dto.response.ReportResponse; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.domain.report.service.ReportQueryService; +import com.votogether.test.ControllerTest; +import io.restassured.common.mapper.TypeRef; +import io.restassured.http.ContentType; +import io.restassured.module.mockmvc.RestAssuredMockMvc; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +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.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(ReportQueryController.class) +class ReportQueryControllerTest extends ControllerTest { + + @MockBean + ReportQueryService reportQueryService; + + @Autowired + MockMvc mockMvc; + + @BeforeEach + void setUp() { + RestAssuredMockMvc.mockMvc(mockMvc); + } + + @Nested + @DisplayName("신고 조치 예정 목록 조회") + class GetReports { + + @Test + @DisplayName("신고 조치 예정 목록을 조회에 성공하면 200 응답을 반환한다.") + void getReports() throws Exception { + // given + mockingAuthArgumentResolver(); + + final int totalPages = 3; + final int currentPageNumber = 1; + final String reason = "reason"; + final ReportType reportType = ReportType.POST; + final long targetId = 1L; + + final ReportAggregateDto reportAggregateDto = new ReportAggregateDto( + 1L, + reportType, + targetId, + reason, + LocalDateTime.now() + ); + + final ReportPageResponse reportPageResponse = + ReportPageResponse.of( + totalPages, + currentPageNumber, + List.of(ReportResponse.of(reportAggregateDto, Long.toString(targetId))) + ); + + int page = 0; + given(reportQueryService.getReports(page)).willReturn(reportPageResponse); + + // when + final ReportPageResponse reportPageResponses = RestAssuredMockMvc + .given().log().all() + .contentType(ContentType.JSON) + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", page) + .when().get("/reports/admin") + .then().log().all() + .status(HttpStatus.OK) + .extract() + .as(new TypeRef<>() { + }); + + // then + final List reports = reportPageResponses.reports(); + final ReportResponse reportResponse = reports.get(0); + assertSoftly(softly -> { + softly.assertThat(reportPageResponses.totalPageNumber()).isEqualTo(totalPages); + softly.assertThat(reportPageResponses.currentPageNumber()).isEqualTo(currentPageNumber); + softly.assertThat(reports).hasSize(1); + softly.assertThat(reportResponse.target()).isEqualTo(Long.toString(targetId)); + softly.assertThat(reportResponse.type()).isEqualTo(reportType); + softly.assertThat(reportResponse.reasons()).containsExactly(reason); + }); + } + + @Test + @DisplayName("페이지가 음수라면 400 응답을 반환한다.") + void return400pageNegative() throws Exception { + // given + mockingAuthArgumentResolver(); + + // when, then + RestAssuredMockMvc.given().log().all() + .contentType(ContentType.JSON) + .headers(HttpHeaders.AUTHORIZATION, "Bearer token") + .param("page", -1) + .when().get("/reports/admin") + .then().log().all() + .status(HttpStatus.BAD_REQUEST) + .body("code", equalTo(201)) + .body("message", containsString("페이지는 0이상 정수만 가능합니다.")); + } + + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/repository/ReportCustomRepositoryImplTest.java b/backend/src/test/java/com/votogether/domain/report/repository/ReportCustomRepositoryImplTest.java new file mode 100644 index 000000000..28076e3f5 --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/repository/ReportCustomRepositoryImplTest.java @@ -0,0 +1,92 @@ +package com.votogether.domain.report.repository; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.repository.MemberRepository; +import com.votogether.domain.report.dto.ReportAggregateDto; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.test.RepositoryTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +class ReportCustomRepositoryImplTest extends RepositoryTest { + + @Autowired + ReportCustomRepositoryImpl reportCustomRepository; + + @Autowired + MemberRepository memberRepository; + + @Test + @DisplayName("신고 조치 예정 목록을 최신순으로 조회한다") + void getReports() { + // given + final Member member = memberRepository.save(MemberFixtures.MALE_30.get()); + + final Report savedReportA = reportTestPersister.builder() + .member(member) + .reportType(ReportType.POST) + .reason("reasonA") + .targetId(1L) + .save(); + + final Report savedReportB = reportTestPersister.builder() + .member(member) + .reportType(ReportType.COMMENT) + .reason("reasonB") + .targetId(1L) + .save(); + + final ReportAggregateDto reportAggregateDtoA = new ReportAggregateDto( + savedReportA.getId(), + savedReportA.getReportType(), + savedReportA.getTargetId(), + savedReportA.getReason(), + savedReportA.getCreatedAt() + ); + + final ReportAggregateDto reportAggregateDtoB = new ReportAggregateDto( + savedReportB.getId(), + savedReportB.getReportType(), + savedReportB.getTargetId(), + savedReportB.getReason(), + savedReportB.getCreatedAt() + ); + + final PageRequest pageRequest = PageRequest.of(0, 20); + + // when + final List reports = reportCustomRepository + .findReportsGroupedByReportTypeAndTargetId(pageRequest); + + // then + assertSoftly(softly -> { + equalTo(softly, reports.get(0), reportAggregateDtoB); + equalTo(softly, reports.get(1), reportAggregateDtoA); + }); + } + + private void equalTo( + final SoftAssertions softly, + final ReportAggregateDto actualReport, + final ReportAggregateDto expectReport + ) { + softly.assertThat(actualReport).usingRecursiveComparison() + .withEqualsForType( + (createdAtA, createdAtB) -> createdAtA.truncatedTo(ChronoUnit.SECONDS) + .isEqual(createdAtB.truncatedTo(ChronoUnit.SECONDS)), + LocalDateTime.class) + .ignoringFields("member", "updatedAt") + .isEqualTo(expectReport); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/service/ReportQueryServiceTest.java b/backend/src/test/java/com/votogether/domain/report/service/ReportQueryServiceTest.java new file mode 100644 index 000000000..577d157ca --- /dev/null +++ b/backend/src/test/java/com/votogether/domain/report/service/ReportQueryServiceTest.java @@ -0,0 +1,70 @@ +package com.votogether.domain.report.service; + +import static org.assertj.core.api.SoftAssertions.assertSoftly; + +import com.votogether.domain.member.entity.Member; +import com.votogether.domain.member.service.MemberService; +import com.votogether.domain.post.entity.comment.Comment; +import com.votogether.domain.report.dto.response.ReportResponse; +import com.votogether.domain.report.dto.response.ReportPageResponse; +import com.votogether.domain.report.entity.Report; +import com.votogether.domain.report.entity.vo.ReportType; +import com.votogether.test.ServiceTest; +import com.votogether.test.fixtures.MemberFixtures; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class ReportQueryServiceTest extends ServiceTest { + + @Autowired + ReportQueryService reportQueryService; + + @Autowired + MemberService memberService; + + @Test + @DisplayName("신고 조치 예정 목록을 최신순으로 조회한다") + void getReports() { + // given + final Member member = memberService.register(MemberFixtures.MALE_30.get()); + + final Comment comment = commentTestPersister.builder() + .content("commnetA") + .save(); + + final Report savedReportA = reportTestPersister.builder() + .member(member) + .reportType(ReportType.POST) + .reason("reasonA") + .targetId(1L) + .save(); + + final Report savedReportB = reportTestPersister.builder() + .member(member) + .reportType(ReportType.COMMENT) + .reason("reasonB") + .targetId(comment.getId()) + .save(); + + // when + final ReportPageResponse reportPageResponse = reportQueryService.getReports(0); + + // then + final List reportResponses = reportPageResponse.reports(); + assertSoftly(softly -> { + softly.assertThat(reportPageResponse.totalPageNumber()).isOne(); + softly.assertThat(reportPageResponse.currentPageNumber()).isZero(); + softly.assertThat(reportResponses.get(0).id()).isEqualTo(savedReportB.getId()); + softly.assertThat(reportResponses.get(0).type()).isEqualTo(savedReportB.getReportType()); + softly.assertThat(reportResponses.get(0).reasons().get(0)).isEqualTo(savedReportB.getReason()); + softly.assertThat(reportResponses.get(0).target()).isEqualTo(comment.getContent()); + softly.assertThat(reportResponses.get(1).id()).isEqualTo(savedReportA.getId()); + softly.assertThat(reportResponses.get(1).type()).isEqualTo(savedReportA.getReportType()); + softly.assertThat(reportResponses.get(1).reasons().get(0)).isEqualTo(savedReportA.getReason()); + softly.assertThat(reportResponses.get(1).target()).isEqualTo(savedReportA.getTargetId().toString()); + }); + } + +} diff --git a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportCommentStrategyTest.java b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportCommentStrategyTest.java index 3067999d5..dfe1a0e39 100644 --- a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportCommentStrategyTest.java +++ b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportCommentStrategyTest.java @@ -129,4 +129,20 @@ void reportAndBlind() { ); } + @Test + @DisplayName("targetId를 통해 comment의 내용을 가져온다") + void parseTarget() { + // given + final String savedCommentContent = "commentA"; + final Comment comment = commentTestPersister.builder() + .content(savedCommentContent) + .save(); + + // when + final String content = reportCommentStrategy.parseTarget(comment.getId()); + + // then + assertThat(content).isEqualTo(savedCommentContent); + } + } diff --git a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategyTest.java b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategyTest.java index 0c47fcfac..80daedfef 100644 --- a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategyTest.java +++ b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportNicknameStrategyTest.java @@ -97,4 +97,17 @@ void reportAndBlind() { ); } + @Test + @DisplayName("targetId를 통해 해당 멤버의 Nickname을 가져온다") + void parseTarget() { + // given + final Member member = memberRepository.save(MemberFixtures.MALE_30.get()); + + // when + final String nickName = reportNicknameStrategy.parseTarget(member.getId()); + + // then + assertThat(nickName).isEqualTo(member.getNickname()); + } + } diff --git a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportPostStrategyTest.java b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportPostStrategyTest.java index 9b37c8bc1..b3278a7dd 100644 --- a/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportPostStrategyTest.java +++ b/backend/src/test/java/com/votogether/domain/report/service/strategy/ReportPostStrategyTest.java @@ -133,4 +133,17 @@ void reportAndBlind() { ); } + @Test + @DisplayName("targetId를 문자열로 파싱한다.") + void parseTarget() { + // given + Post post = postTestPersister.postBuilder().save(); + + // when + final String postId = reportPostStrategy.parseTarget(post.getId()); + + // then + assertThat(postId).isEqualTo(post.getId().toString()); + } + }