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.
316 lines
15 KiB
316 lines
15 KiB
package com.mydreamjob.application;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.mydreamjob.application.dto.CandidateDetailView;
|
|
import com.mydreamjob.application.dto.CandidateItemView;
|
|
import com.mydreamjob.application.dto.CandidateListResponse;
|
|
import com.mydreamjob.auth.User;
|
|
import com.mydreamjob.auth.User.Role;
|
|
import com.mydreamjob.auth.UserRepository;
|
|
import com.mydreamjob.common.exception.BusinessException;
|
|
import com.mydreamjob.common.exception.ErrorCode;
|
|
import com.mydreamjob.company.Company;
|
|
import com.mydreamjob.company.CompanyService;
|
|
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.web.servlet.MockMvc;
|
|
|
|
import java.time.LocalDateTime;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
|
|
import static org.mockito.ArgumentMatchers.*;
|
|
import static org.mockito.Mockito.*;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
|
|
|
@SpringBootTest
|
|
@AutoConfigureMockMvc
|
|
@ActiveProfiles("integration-test")
|
|
class ApplicationControllerTest {
|
|
|
|
@Autowired private MockMvc mockMvc;
|
|
@Autowired private com.mydreamjob.auth.JwtTokenProvider jwtTokenProvider;
|
|
@MockBean private ApplicationService applicationService;
|
|
@MockBean private CompanyService companyService;
|
|
@MockBean private UserRepository userRepository;
|
|
@MockBean private RedisTemplate<String, Object> redisTemplate;
|
|
private final ObjectMapper mapper = new ObjectMapper();
|
|
|
|
private static final Long COMPANY_USER_ID = 10L;
|
|
private static final Long STUDENT_USER_ID = 20L;
|
|
private static final Long ADMIN_USER_ID = 30L;
|
|
private static final Long COMPANY_ID = 1L;
|
|
private static final Long JOB_ID = 50L;
|
|
private static final Long APP_ID = 100L;
|
|
|
|
@BeforeEach
|
|
@SuppressWarnings("unchecked")
|
|
void setUp() {
|
|
ValueOperations<String, Object> ops = mock(ValueOperations.class);
|
|
when(redisTemplate.opsForValue()).thenReturn(ops);
|
|
when(redisTemplate.hasKey(any())).thenReturn(false);
|
|
|
|
// Default: COMPANY user exists
|
|
User companyUser = new User();
|
|
companyUser.setRoles(Set.of(Role.COMPANY));
|
|
when(userRepository.findById(COMPANY_USER_ID)).thenReturn(Optional.of(companyUser));
|
|
|
|
// STUDENT user
|
|
User studentUser = new User();
|
|
studentUser.setRoles(Set.of(Role.STUDENT));
|
|
when(userRepository.findById(STUDENT_USER_ID)).thenReturn(Optional.of(studentUser));
|
|
|
|
// ADMIN user
|
|
User adminUser = new User();
|
|
adminUser.setRoles(Set.of(Role.ADMIN));
|
|
when(userRepository.findById(ADMIN_USER_ID)).thenReturn(Optional.of(adminUser));
|
|
}
|
|
|
|
private String companyToken() {
|
|
return com.mydreamjob.auth.support.TestAuthTokens.bearer(COMPANY_USER_ID, "COMPANY");
|
|
}
|
|
|
|
private String studentToken() {
|
|
return com.mydreamjob.auth.support.TestAuthTokens.bearer(STUDENT_USER_ID, "STUDENT");
|
|
}
|
|
|
|
private String adminToken() {
|
|
return com.mydreamjob.auth.support.TestAuthTokens.bearer(ADMIN_USER_ID, "ADMIN");
|
|
}
|
|
|
|
private void mockCompanyExists() {
|
|
Company company = Company.builder().id(COMPANY_ID).ownerUserId(COMPANY_USER_ID).name("测试企业").build();
|
|
when(companyService.findCompanyByOwnerUserId(COMPANY_USER_ID)).thenReturn(company);
|
|
}
|
|
|
|
// ── 正常访问 ────────────────────────────────────────────────
|
|
|
|
@Test void list_companyNormal_shouldReturn200() throws Exception {
|
|
mockCompanyExists();
|
|
CandidateListResponse resp = CandidateListResponse.builder()
|
|
.items(List.of()).total(0).page(0).size(20).totalPages(0).build();
|
|
when(applicationService.listCompanyCandidates(anyLong(), isNull(), isNull(), isNull(), isNull(), isNull(), anyInt(), anyInt()))
|
|
.thenReturn(resp);
|
|
|
|
mockMvc.perform(get("/company/candidates")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.total").value(0));
|
|
}
|
|
|
|
@Test void detail_companyNormal_shouldReturn200() throws Exception {
|
|
mockCompanyExists();
|
|
CandidateDetailView detail = CandidateDetailView.builder()
|
|
.applicationId(APP_ID).candidateName("张三").build();
|
|
when(applicationService.getCompanyCandidateDetail(COMPANY_ID, APP_ID)).thenReturn(detail);
|
|
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID)
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.candidateName").value("张三"));
|
|
}
|
|
|
|
// ── 权限 ────────────────────────────────────────────────────
|
|
|
|
@Test void list_noToken_shouldReturn401() throws Exception {
|
|
mockMvc.perform(get("/company/candidates"))
|
|
.andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void list_student_shouldReturn403() throws Exception {
|
|
mockMvc.perform(get("/company/candidates")
|
|
.header("Authorization", studentToken()))
|
|
.andExpect(status().isForbidden());
|
|
}
|
|
|
|
@Test void list_admin_shouldReturn403() throws Exception {
|
|
mockMvc.perform(get("/company/candidates")
|
|
.header("Authorization", adminToken()))
|
|
.andExpect(status().isForbidden());
|
|
}
|
|
|
|
@Test void detail_student_shouldReturn403() throws Exception {
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID)
|
|
.header("Authorization", studentToken()))
|
|
.andExpect(status().isForbidden());
|
|
}
|
|
|
|
@Test void detail_noToken_shouldReturn401() throws Exception {
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID))
|
|
.andExpect(status().isUnauthorized());
|
|
}
|
|
|
|
@Test void list_companyNotFound_shouldReturn404() throws Exception {
|
|
when(companyService.findCompanyByOwnerUserId(COMPANY_USER_ID)).thenReturn(null);
|
|
|
|
mockMvc.perform(get("/company/candidates")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isNotFound())
|
|
.andExpect(jsonPath("$.code").value(5001));
|
|
}
|
|
|
|
// ── 越权 ────────────────────────────────────────────────────
|
|
|
|
@Test void detail_wrongCompany_shouldReturn403() throws Exception {
|
|
mockCompanyExists();
|
|
when(applicationService.getCompanyCandidateDetail(COMPANY_ID, APP_ID))
|
|
.thenThrow(new BusinessException(ErrorCode.APPLICATION_OWNER_MISMATCH));
|
|
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID)
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isForbidden())
|
|
.andExpect(jsonPath("$.code").value(8004));
|
|
}
|
|
|
|
// ── 参数校验-分页 ────────────────────────────────────────────
|
|
|
|
@Test void list_pageNegative_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?page=-1")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
@Test void list_sizeZero_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?size=0")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
@Test void list_sizeTooLarge_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?size=101")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
// ── 参数校验-status ──────────────────────────────────────────
|
|
|
|
@Test void list_invalidStatus_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?status=INVALID")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
// ── 参数校验-sort/order ──────────────────────────────────────
|
|
|
|
@Test void list_invalidSort_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?sort=invalid")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
@Test void list_invalidOrder_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?order=invalid")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
// ── 参数校验-keyword ─────────────────────────────────────────
|
|
|
|
@Test void list_keywordTooLong_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
String longKeyword = "a".repeat(101);
|
|
mockMvc.perform(get("/company/candidates?keyword=" + longKeyword)
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
// ── 参数校验-jobId ───────────────────────────────────────────
|
|
|
|
@Test void list_jobIdZero_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?jobId=0")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
@Test void list_jobIdNegative_shouldReturn400() throws Exception {
|
|
mockCompanyExists();
|
|
mockMvc.perform(get("/company/candidates?jobId=-1")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isBadRequest())
|
|
.andExpect(jsonPath("$.code").value(1001));
|
|
}
|
|
|
|
// ── 详情-不存在 ─────────────────────────────────────────────
|
|
|
|
@Test void detail_appNotFound_shouldReturn404() throws Exception {
|
|
mockCompanyExists();
|
|
when(applicationService.getCompanyCandidateDetail(COMPANY_ID, APP_ID))
|
|
.thenThrow(new BusinessException(ErrorCode.APPLICATION_NOT_FOUND));
|
|
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID)
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isNotFound())
|
|
.andExpect(jsonPath("$.code").value(8002));
|
|
}
|
|
|
|
// ── 敏感字段隔离 ────────────────────────────────────────────
|
|
|
|
@Test void list_shouldNotExposeSensitiveFields() throws Exception {
|
|
mockCompanyExists();
|
|
CandidateItemView item = CandidateItemView.builder()
|
|
.applicationId(APP_ID).jobId(JOB_ID).jobTitle("Java").status("SUBMITTED")
|
|
.candidateName("张三").candidateSchool("清华").candidateEducation("本科")
|
|
.candidateGraduationYear((short)2026).candidateAvatarUrl("https://img/1.png")
|
|
.createdAt(LocalDateTime.now()).updatedAt(LocalDateTime.now()).build();
|
|
CandidateListResponse resp = CandidateListResponse.builder()
|
|
.items(List.of(item)).total(1).page(0).size(20).totalPages(1).build();
|
|
when(applicationService.listCompanyCandidates(anyLong(), isNull(), isNull(), isNull(), isNull(), isNull(), anyInt(), anyInt()))
|
|
.thenReturn(resp);
|
|
|
|
mockMvc.perform(get("/company/candidates")
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.items[0].candidateName").value("张三"))
|
|
.andExpect(jsonPath("$.data.items[0].studentUserId").doesNotExist())
|
|
.andExpect(jsonPath("$.data.items[0].phone").doesNotExist());
|
|
}
|
|
|
|
@Test void detail_shouldNotExposeSensitiveFields_butIncludeCoverLetter() throws Exception {
|
|
mockCompanyExists();
|
|
CandidateDetailView detail = CandidateDetailView.builder()
|
|
.applicationId(APP_ID).jobId(JOB_ID).jobTitle("Java")
|
|
.jobCity("北京").jobEmploymentType("全职").status("SUBMITTED")
|
|
.coverLetter("这是我的自我介绍")
|
|
.candidateName("张三").candidateAvatarUrl("https://img/1.png")
|
|
.candidateBio("bio").candidateLocation("北京").candidateSchool("清华")
|
|
.candidateEducation("本科").candidateGraduationYear((short)2026)
|
|
.candidateSkills(List.of("Java", "Spring"))
|
|
.createdAt(LocalDateTime.now()).updatedAt(LocalDateTime.now())
|
|
.build();
|
|
when(applicationService.getCompanyCandidateDetail(COMPANY_ID, APP_ID)).thenReturn(detail);
|
|
|
|
mockMvc.perform(get("/company/candidates/" + APP_ID)
|
|
.header("Authorization", companyToken()))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.coverLetter").value("这是我的自我介绍"))
|
|
.andExpect(jsonPath("$.data.phone").doesNotExist())
|
|
.andExpect(jsonPath("$.data.passwordHash").doesNotExist())
|
|
.andExpect(jsonPath("$.data.resumeUrl").doesNotExist());
|
|
}
|
|
}
|