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.
 
 
 

137 lines
6.5 KiB

package com.mydreamjob.talentdiscovery;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mydreamjob.auth.JwtTokenProvider;
import com.mydreamjob.common.exception.BusinessException;
import com.mydreamjob.common.exception.ErrorCode;
import com.mydreamjob.talentdiscovery.dto.TalentDiscoveryCandidateResponse;
import com.mydreamjob.talentdiscovery.dto.TalentDiscoveryPageResponse;
import com.mydreamjob.talentdiscovery.dto.TalentDiscoveryPreferenceResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("integration-test")
@TestPropertySource(properties = "spring.jpa.open-in-view=false")
class TalentDiscoveryControllerTest {
@Autowired private MockMvc mockMvc;
@Autowired private JwtTokenProvider jwtTokenProvider;
@MockBean private TalentDiscoveryService service;
@MockBean private RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper mapper = new ObjectMapper();
@BeforeEach
@SuppressWarnings("unchecked")
void setUp() {
ValueOperations<String, Object> ops = org.mockito.Mockito.mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(ops);
when(redisTemplate.hasKey(any())).thenReturn(false);
}
private String token(long userId, String role) {
return com.mydreamjob.auth.support.TestAuthTokens.bearer(userId, role);
}
@Test
void getPreference_usesJwtIdentityAndReturnsPreference() throws Exception {
when(service.getPreference(100L)).thenReturn(TalentDiscoveryPreferenceResponse.builder()
.enabled(false).version(0L).build());
mockMvc.perform(get("/profile/me/talent-discovery")
.header("Authorization", token(100L, "STUDENT")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.enabled").value(false))
.andExpect(jsonPath("$.data.version").value(0));
}
@Test
void getPreference_withoutTokenReturns401() throws Exception {
mockMvc.perform(get("/profile/me/talent-discovery"))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.code").value(401));
}
@Test
void updatePreference_studentCannotSupplyIdentityAndReturns200() throws Exception {
when(service.updatePreference(anyLong(), any(), any())).thenReturn(
TalentDiscoveryPreferenceResponse.builder().enabled(true).version(1L).build());
String body = mapper.writeValueAsString(java.util.Map.of(
"enabled", true, "expectedVersion", 0, "studentUserId", 999999));
mockMvc.perform(put("/profile/me/talent-discovery")
.header("Authorization", token(100L, "STUDENT"))
.contentType(MediaType.APPLICATION_JSON)
.content(body))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.enabled").value(true));
}
@Test
void companyRecommendationResponseContainsOnlyWhitelistedCardFields() throws Exception {
TalentDiscoveryCandidateResponse candidate = TalentDiscoveryCandidateResponse.builder()
.candidateKey("opaque-key")
.candidateLabel("开放人才候选人")
.city("杭州")
.education("本科")
.graduationYear(2026)
.skills(List.of("Java", "Spring Boot"))
.fitBand("HIGH")
.summary("岗位画像与候选人公开资料存在较高匹配度")
.signalsAvailable(List.of("PREFERENCE", "RIASEC", "ABILITY"))
.build();
when(service.recommendations(anyLong(), anyLong(), anyInt(), anyInt())).thenReturn(
TalentDiscoveryPageResponse.builder().mode("PERSONALIZED")
.items(List.of(candidate)).total(1).page(0).size(20).totalPages(1).build());
mockMvc.perform(get("/companies/me/jobs/9/talent-recommendations")
.header("Authorization", token(200L, "COMPANY")))
.andExpect(status().isOk())
.andExpect(jsonPath("$.data.items[0].candidateKey").value("opaque-key"))
.andExpect(jsonPath("$.data.items[0].fitScore").doesNotExist())
.andExpect(content().string(not(containsString("studentUserId"))))
.andExpect(content().string(not(containsString("dimensionScores"))))
.andExpect(content().string(not(containsString("overallScore"))))
.andExpect(content().string(not(containsString("fitScore"))));
}
@Test
void companyRecommendationServiceForbiddenIsMappedTo403() throws Exception {
doThrow(new BusinessException(ErrorCode.FORBIDDEN))
.when(service).recommendations(anyLong(), anyLong(), anyInt(), anyInt());
mockMvc.perform(get("/companies/me/jobs/9/talent-recommendations")
.header("Authorization", token(201L, "COMPANY")))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.code").value(2002));
}
}