package com.mydreamjob.resume; import com.mydreamjob.auth.User; import com.mydreamjob.auth.UserRepository; 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.mock.web.MockMultipartFile; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import java.util.Map; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * 简历版本权限一致性集成测试(真实 Service + 真实 H2)。 * *

版本存在但属于他人 → 四个接口一律 HTTP 403;版本不存在 → 404。 * 覆盖:GET/PUT/DELETE /profile/me/resume-versions/{id} 与 POST .../default。 */ @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("integration-test") class ResumeVersionPermissionControllerTest { @Autowired private MockMvc mockMvc; @Autowired private com.mydreamjob.auth.JwtTokenProvider jwtTokenProvider; @Autowired private ResumeVersionService resumeVersionService; @Autowired private UserRepository userRepository; @MockBean private RedisTemplate redisTemplate; private Long ownerUserId; private Long otherUserId; private Long versionId; private static final java.util.concurrent.atomic.AtomicLong PHONE_SEQ = new java.util.concurrent.atomic.AtomicLong(300); @BeforeEach @SuppressWarnings("unchecked") void setUp() { ValueOperations ops = mock(ValueOperations.class); when(redisTemplate.opsForValue()).thenReturn(ops); when(redisTemplate.hasKey(any())).thenReturn(false); ownerUserId = createStudent(); otherUserId = createStudent(); MockMultipartFile file = new MockMultipartFile("file", "r.pdf", "application/pdf", "%PDF-1.7\npermission-fixture\n%%EOF\n".getBytes(java.nio.charset.StandardCharsets.US_ASCII)); ResumeVersion version = resumeVersionService.createVersion(ownerUserId, "秋招版", file); versionId = version.getId(); } private Long createStudent() { User u = userRepository.save(User.builder() .phone("177" + String.format("%08d", PHONE_SEQ.incrementAndGet())) .nickname("候选人").role(User.Role.STUDENT).active(true).verified(true).build()); return u.getId(); } private String token(Long userId) { return com.mydreamjob.auth.support.TestAuthTokens.bearer(userId, "STUDENT"); } // ── 另一学生访问 → 403 ──────────────────────────────────── @Test void get_otherStudent_shouldReturn403() throws Exception { mockMvc.perform(get("/profile/me/resume-versions/" + versionId) .header("Authorization", token(otherUserId))) .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(com.mydreamjob.common.exception.ErrorCode.FORBIDDEN.getCode())); } @Test void update_otherStudent_shouldReturn403() throws Exception { mockMvc.perform(MockMvcRequestBuilders.multipart("/profile/me/resume-versions/" + versionId) .param("name", "改他人") .with(req -> { req.setMethod("PUT"); return req; }) .header("Authorization", token(otherUserId))) .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(com.mydreamjob.common.exception.ErrorCode.FORBIDDEN.getCode())); } @Test void delete_otherStudent_shouldReturn403() throws Exception { mockMvc.perform(delete("/profile/me/resume-versions/" + versionId) .header("Authorization", token(otherUserId))) .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(com.mydreamjob.common.exception.ErrorCode.FORBIDDEN.getCode())); } @Test void setDefault_otherStudent_shouldReturn403() throws Exception { mockMvc.perform(post("/profile/me/resume-versions/" + versionId + "/default") .header("Authorization", token(otherUserId))) .andExpect(status().isForbidden()) .andExpect(jsonPath("$.code").value(com.mydreamjob.common.exception.ErrorCode.FORBIDDEN.getCode())); } // ── 不存在 → 404 ────────────────────────────────────────── @Test void get_notExist_shouldReturn404() throws Exception { mockMvc.perform(get("/profile/me/resume-versions/999999") .header("Authorization", token(otherUserId))) .andExpect(status().isNotFound()) .andExpect(jsonPath("$.code").value(com.mydreamjob.common.exception.ErrorCode.RESUME_VERSION_NOT_FOUND.getCode())); } // ── 本人 → 200(基线,确认规则未误伤本人) ───────────────── @Test void get_owner_shouldReturn200() throws Exception { mockMvc.perform(get("/profile/me/resume-versions/" + versionId) .header("Authorization", token(ownerUserId))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.id").value(versionId)); } }