|
|
|
@ -24,12 +24,14 @@ import org.springframework.data.domain.PageImpl; |
|
|
|
import org.springframework.data.domain.Pageable; |
|
|
|
|
|
|
|
import java.util.*; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.*; |
|
|
|
import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.ArgumentMatchers.anyLong; |
|
|
|
import static org.mockito.ArgumentMatchers.anyString; |
|
|
|
import static org.mockito.ArgumentMatchers.anyList; |
|
|
|
import static org.mockito.ArgumentMatchers.argThat; |
|
|
|
import static org.mockito.Mockito.*; |
|
|
|
|
|
|
|
@ExtendWith(MockitoExtension.class) |
|
|
|
@ -292,6 +294,49 @@ class ApplicationServiceTest { |
|
|
|
assertEquals(1, resp.getItems().size()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test void listCompanyCandidates_shouldComputeMatchPercentile() { |
|
|
|
// 同一岗位 3 名候选人,matchScore 90/60/30 → 百分位 100/50/0(口径 countLower/(n-1))
|
|
|
|
Object[] high = buildRow(200L, JOB_ID, "Java", "SUBMITTED", "张三", "清华", "本科", null, null); |
|
|
|
Object[] mid = buildRow(201L, JOB_ID, "Java", "SUBMITTED", "李四", "北大", "硕士", null, null); |
|
|
|
Object[] low = buildRow(202L, JOB_ID, "Java", "SUBMITTED", "王五", "复旦", "本科", null, null); |
|
|
|
StudentProfile spHigh = (StudentProfile) high[2]; |
|
|
|
StudentProfile spMid = (StudentProfile) mid[2]; |
|
|
|
StudentProfile spLow = (StudentProfile) low[2]; |
|
|
|
|
|
|
|
when(applicationRepository.findCompanyCandidates(anyLong(), any(), any(), any(), any(Pageable.class))) |
|
|
|
.thenReturn(mockCandidatePage(high, mid, low)); |
|
|
|
// percentile cohort = 该岗位全量候选人(同岗位 3 人)
|
|
|
|
when(applicationRepository.findCompanyCandidatesForMatch(anyLong(), any(), any(), any())) |
|
|
|
.thenReturn(List.of(high, mid, low)); |
|
|
|
when(talentMatchScorer.match(any(), argThat(sp -> sp == spHigh), any())) |
|
|
|
.thenReturn(new TalentMatchScorer.MatchResult(90, List.of("x"))); |
|
|
|
when(talentMatchScorer.match(any(), argThat(sp -> sp == spMid), any())) |
|
|
|
.thenReturn(new TalentMatchScorer.MatchResult(60, List.of("x"))); |
|
|
|
when(talentMatchScorer.match(any(), argThat(sp -> sp == spLow), any())) |
|
|
|
.thenReturn(new TalentMatchScorer.MatchResult(30, List.of("x"))); |
|
|
|
|
|
|
|
PageResponse<CandidateItemView> resp = applicationService.listCompanyCandidates( |
|
|
|
COMPANY_ID, null, null, null, null, null, 0, 20); |
|
|
|
Map<Long, Integer> pctByApp = resp.getItems().stream() |
|
|
|
.collect(Collectors.toMap(CandidateItemView::getApplicationId, CandidateItemView::getMatchPercentile)); |
|
|
|
assertEquals(Integer.valueOf(100), pctByApp.get(200L)); // 2/(3-1)*100
|
|
|
|
assertEquals(Integer.valueOf(50), pctByApp.get(201L)); // 1/(3-1)*100
|
|
|
|
assertEquals(Integer.valueOf(0), pctByApp.get(202L)); // 0/(3-1)*100
|
|
|
|
} |
|
|
|
|
|
|
|
@Test void listCompanyCandidates_cohortLessThan2_shouldOmitPercentile() { |
|
|
|
// 同岗位仅 1 名候选人 → 百分位不可计算 → null(配合 NON_NULL 省略)
|
|
|
|
Object[] row = buildRow(200L, JOB_ID, "Java", "SUBMITTED", "张三", "清华", "本科", null, null); |
|
|
|
when(applicationRepository.findCompanyCandidates(anyLong(), any(), any(), any(), any(Pageable.class))) |
|
|
|
.thenReturn(mockCandidatePage(row)); |
|
|
|
when(applicationRepository.findCompanyCandidatesForMatch(anyLong(), any(), any(), any())) |
|
|
|
.thenReturn(Collections.singletonList(row)); |
|
|
|
|
|
|
|
PageResponse<CandidateItemView> resp = applicationService.listCompanyCandidates( |
|
|
|
COMPANY_ID, null, null, null, null, null, 0, 20); |
|
|
|
assertNull(resp.getItems().get(0).getMatchPercentile()); |
|
|
|
} |
|
|
|
|
|
|
|
// ── 企业候选人详情 ─────────────────────────────────────────
|
|
|
|
|
|
|
|
@Test void getCompanyCandidateDetail_shouldReturnDetail() { |
|
|
|
@ -322,10 +367,42 @@ class ApplicationServiceTest { |
|
|
|
assertEquals("清华", detail.getCandidateSchool()); |
|
|
|
assertEquals("13800000001", detail.getCandidatePhone()); |
|
|
|
assertEquals("stu@example.com", detail.getCandidateEmail()); |
|
|
|
// 详情一致返回岗位匹配度与理由(企业内部分数,与列表面板同源)
|
|
|
|
assertEquals(Integer.valueOf(88), detail.getMatchScore()); |
|
|
|
assertEquals(List.of("x"), detail.getMatchReasons()); |
|
|
|
// 成功详情查询不产生额外 Repository 查询(无 N+1)
|
|
|
|
verify(applicationRepository, never()).findById(anyLong()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test void getCompanyCandidateDetail_shouldComputeMatchPercentile() { |
|
|
|
// 目标候选人同岗位另有 1 人且 matchScore 更低 → 百分位 round(1/(2-1)*100)=100
|
|
|
|
Application app = Application.builder().id(APP_ID).jobId(JOB_ID) |
|
|
|
.studentUserId(STUDENT_ID).status("SUBMITTED").build(); |
|
|
|
com.mydreamjob.job.Job job = com.mydreamjob.job.Job.builder() |
|
|
|
.id(JOB_ID).companyId(COMPANY_ID).title("Java").build(); |
|
|
|
StudentProfile mine = StudentProfile.builder().userId(STUDENT_ID).realName("张三").build(); |
|
|
|
Object[] myRow = new Object[]{app, job, mine, null}; |
|
|
|
com.mydreamjob.auth.User otherUser = mock(com.mydreamjob.auth.User.class); |
|
|
|
StudentProfile other = StudentProfile.builder().userId(999L).realName("李四").build(); |
|
|
|
Object[] otherRow = new Object[]{ |
|
|
|
Application.builder().id(999L).jobId(JOB_ID).studentUserId(999L).status("SUBMITTED").build(), |
|
|
|
job, other, otherUser}; |
|
|
|
|
|
|
|
when(applicationRepository.findCandidateDetailById(APP_ID)).thenReturn(Collections.singletonList(myRow)); |
|
|
|
when(privacySettingsService.canCompanyViewResume(STUDENT_ID, COMPANY_ID, APP_ID)).thenReturn(false); |
|
|
|
when(privacySettingsService.canViewContact(STUDENT_ID)).thenReturn(false); |
|
|
|
// percentile cohort = 该岗位全量候选人(目标 + 同岗位另 1 人)
|
|
|
|
when(applicationRepository.findCompanyCandidatesForMatch(anyLong(), any(), any(), any())) |
|
|
|
.thenReturn(List.of(myRow, otherRow)); |
|
|
|
when(talentMatchScorer.match(any(), argThat(sp -> sp == mine), any())) |
|
|
|
.thenReturn(new TalentMatchScorer.MatchResult(90, List.of("x"))); |
|
|
|
when(talentMatchScorer.match(any(), argThat(sp -> sp == other), any())) |
|
|
|
.thenReturn(new TalentMatchScorer.MatchResult(40, List.of("x"))); |
|
|
|
|
|
|
|
CandidateDetailView detail = applicationService.getCompanyCandidateDetail(COMPANY_ID, APP_ID); |
|
|
|
assertEquals(Integer.valueOf(100), detail.getMatchPercentile()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test void getCompanyCandidateDetail_wrongCompany_shouldThrow() { |
|
|
|
// 投递存在,但岗位 companyId 与当前企业不匹配 → APPLICATION_OWNER_MISMATCH
|
|
|
|
Application app = Application.builder().id(APP_ID).jobId(JOB_ID) |
|
|
|
|