You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
151 lines
7.1 KiB
151 lines
7.1 KiB
package com.mydreamjob.review;
|
|
|
|
import com.mydreamjob.audit.AuditService;
|
|
import com.mydreamjob.common.exception.BusinessException;
|
|
import com.mydreamjob.common.exception.ErrorCode;
|
|
import com.mydreamjob.company.Company;
|
|
import com.mydreamjob.company.CompanyService;
|
|
import com.mydreamjob.job.Job;
|
|
import com.mydreamjob.job.JobRepository;
|
|
import com.mydreamjob.review.dto.CreateCompanyReviewReplyRequest;
|
|
import com.mydreamjob.review.dto.UpdateCompanyReviewReplyRequest;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
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;
|
|
import org.mockito.junit.jupiter.MockitoSettings;
|
|
import org.mockito.quality.Strictness;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.List;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
import static org.mockito.Mockito.*;
|
|
|
|
@ExtendWith(MockitoExtension.class)
|
|
@MockitoSettings(strictness = Strictness.LENIENT)
|
|
class CompanyReviewReplyServiceTest {
|
|
|
|
@Mock private CompanyService companyService;
|
|
@Mock private JobReviewRepository reviewRepository;
|
|
@Mock private JobRepository jobRepository;
|
|
@Mock private CompanyReviewReplyRepository replyRepository;
|
|
@Mock private CompanyReviewReplyHistoryRepository historyRepository;
|
|
@Mock private ReviewModerationService moderationService;
|
|
@Mock private AuditService auditService;
|
|
@Mock private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
|
@InjectMocks private CompanyReviewReplyService service;
|
|
|
|
private static final long USER_ID = 11L;
|
|
private static final long COMPANY_ID = 21L;
|
|
private static final long JOB_ID = 31L;
|
|
private static final long REVIEW_ID = 41L;
|
|
private final String key = UUID.randomUUID().toString();
|
|
private Company company;
|
|
private JobReview review;
|
|
private Job job;
|
|
|
|
@BeforeEach
|
|
void setUp() throws Exception {
|
|
company = Company.builder().id(COMPANY_ID).ownerUserId(USER_ID).name("Acme").build();
|
|
review = JobReview.builder().id(REVIEW_ID).jobId(JOB_ID).studentUserId(99L)
|
|
.rating(4).content("评价").status("PUBLISHED").build();
|
|
job = Job.builder().id(JOB_ID).companyId(COMPANY_ID).title("后端工程师").build();
|
|
when(companyService.findCompanyByOwnerUserId(USER_ID)).thenReturn(company);
|
|
when(reviewRepository.findById(REVIEW_ID)).thenReturn(Optional.of(review));
|
|
when(jobRepository.findById(JOB_ID)).thenReturn(Optional.of(job));
|
|
when(replyRepository.findByCompanyIdAndIdempotencyKey(any(), any())).thenReturn(Optional.empty());
|
|
when(replyRepository.findByReviewId(REVIEW_ID)).thenReturn(Optional.empty());
|
|
when(objectMapper.writeValueAsString(any())).thenReturn("[]");
|
|
when(replyRepository.saveAndFlush(any())).thenAnswer(invocation -> {
|
|
CompanyReviewReply reply = invocation.getArgument(0);
|
|
if (reply.getId() == null) reply.setId(501L);
|
|
return reply;
|
|
});
|
|
}
|
|
|
|
private ReviewModerationService.ModerationResult autoApproved() {
|
|
return new ReviewModerationService.ModerationResult(
|
|
CompanyReviewReply.AUTO_APPROVED, List.of(), BigDecimal.ZERO, null);
|
|
}
|
|
|
|
private ReviewModerationService.ModerationResult pending() {
|
|
return new ReviewModerationService.ModerationResult(
|
|
CompanyReviewReply.PENDING, List.of(ReviewModerationService.REASON_CONTACT_INFO),
|
|
BigDecimal.valueOf(2), "MEDIUM");
|
|
}
|
|
|
|
private CreateCompanyReviewReplyRequest createRequest(String content) {
|
|
CreateCompanyReviewReplyRequest request = new CreateCompanyReviewReplyRequest();
|
|
request.setContent(content);
|
|
request.setIdempotencyKey(key);
|
|
return request;
|
|
}
|
|
|
|
@Test
|
|
void safeTextIsAutoApprovedAndAudited() {
|
|
when(moderationService.moderateCompanyReply("感谢反馈")).thenReturn(autoApproved());
|
|
|
|
var response = service.create(USER_ID, REVIEW_ID, createRequest("感谢反馈"), null);
|
|
|
|
assertEquals(CompanyReviewReply.AUTO_APPROVED, response.getStatus());
|
|
verify(historyRepository, times(2)).save(any());
|
|
verify(auditService, times(2)).log(any(), any(), any(), any(), any(), any());
|
|
}
|
|
|
|
@Test
|
|
void riskyTextBecomesPendingWithoutExposingReasonToCompany() {
|
|
when(moderationService.moderateCompanyReply("请联系 13800138000")).thenReturn(pending());
|
|
|
|
var response = service.create(USER_ID, REVIEW_ID, createRequest("请联系 13800138000"), null);
|
|
|
|
assertEquals(CompanyReviewReply.PENDING, response.getStatus());
|
|
assertEquals(null, response.getRejectionReason());
|
|
verify(auditService).log(any(), eq("COMPANY_REVIEW_REPLY_CREATED"), any(), any(), any(), any());
|
|
}
|
|
|
|
@Test
|
|
void existingReplyCannotBeCreatedAgain() {
|
|
when(replyRepository.findByReviewId(REVIEW_ID)).thenReturn(Optional.of(
|
|
CompanyReviewReply.builder().id(501L).reviewId(REVIEW_ID)
|
|
.companyId(COMPANY_ID).moderationStatus(CompanyReviewReply.PENDING).content("旧回复").build()));
|
|
BusinessException ex = assertThrows(BusinessException.class,
|
|
() -> service.create(USER_ID, REVIEW_ID, createRequest("新回复"), null));
|
|
assertEquals(ErrorCode.REVIEW_REPLY_ALREADY_EXISTS.getCode(), ex.getErrorCodeInt());
|
|
}
|
|
|
|
@Test
|
|
void rejectedReplyCanBeResubmittedWithExpectedVersion() {
|
|
CompanyReviewReply rejected = CompanyReviewReply.builder().id(501L).reviewId(REVIEW_ID)
|
|
.companyId(COMPANY_ID).content("旧回复").moderationStatus(CompanyReviewReply.REJECTED)
|
|
.version(2L).build();
|
|
when(replyRepository.findByReviewId(REVIEW_ID)).thenReturn(Optional.of(rejected));
|
|
when(moderationService.moderateCompanyReply("修订后回复")).thenReturn(autoApproved());
|
|
UpdateCompanyReviewReplyRequest request = new UpdateCompanyReviewReplyRequest();
|
|
request.setContent("修订后回复");
|
|
request.setExpectedVersion(2L);
|
|
|
|
var response = service.revise(USER_ID, REVIEW_ID, request, null);
|
|
|
|
assertEquals("修订后回复", response.getContent());
|
|
assertEquals(CompanyReviewReply.AUTO_APPROVED, response.getStatus());
|
|
verify(historyRepository, times(2)).save(any());
|
|
verify(auditService).log(any(), eq("COMPANY_REVIEW_REPLY_RESUBMITTED"), any(), any(), any(), any());
|
|
}
|
|
|
|
@Test
|
|
void idempotencyKeyWithDifferentPayloadIsConflict() {
|
|
when(replyRepository.findByCompanyIdAndIdempotencyKey(COMPANY_ID, key)).thenReturn(Optional.of(
|
|
CompanyReviewReply.builder().id(501L).reviewId(999L).companyId(COMPANY_ID)
|
|
.content("其他内容").build()));
|
|
BusinessException ex = assertThrows(BusinessException.class,
|
|
() -> service.create(USER_ID, REVIEW_ID, createRequest("感谢反馈"), null));
|
|
assertEquals(ErrorCode.REVIEW_REPLY_IDEMPOTENCY_CONFLICT.getCode(), ex.getErrorCodeInt());
|
|
}
|
|
}
|
|
|