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

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