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.
 
 
 

124 lines
6.2 KiB

package com.mydreamjob.common.r01a5;
import com.mydreamjob.candidatescore.governance.CandidateScoringGovernanceService;
import com.mydreamjob.interviewscore.InterviewScoreService;
import com.mydreamjob.job.JobFavoriteService;
import com.mydreamjob.review.AdminReviewReplyService;
import com.mydreamjob.review.CompanyReviewManagementService;
import com.mydreamjob.review.JobReviewService;
import com.mydreamjob.review.StudentReviewWeightService;
import org.junit.jupiter.api.DisplayName;
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.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* R01-A5 独立复验:**失败请求的业务副作用**。
*
* <p>使用 H2 行级快照(显式列 + 稳定 ORDER BY,逐字段比较,非 COUNT/SUM),
* 验证非法身份请求前后与本批端点相关的核心表完全不变。</p>
*
* <p>说明:本轮不启动 Docker,H2 独立复验;不声称真实 MySQL 结论。</p>
*/
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("integration-test")
class A5SideEffectSnapshotTest {
@Autowired private MockMvc mockMvc;
@Autowired private JdbcTemplate jdbcTemplate;
@MockBean private CandidateScoringGovernanceService governanceService;
@MockBean private InterviewScoreService interviewScoreService;
@MockBean private JobFavoriteService favoriteService;
@MockBean private JobReviewService reviewService;
@MockBean private AdminReviewReplyService adminService;
@MockBean private CompanyReviewManagementService managementService;
@MockBean private StudentReviewWeightService weightService;
@MockBean private RedisTemplate<String, Object> redisTemplate;
/** (表, 主键列, 全列) —— 逐字段快照。 */
private static final List<String[]> TABLES = List.of(
new String[]{"JOB_FAVORITES", "ID", "ID, JOB_ID, STUDENT_USER_ID, CREATED_AT"},
new String[]{"JOB_REVIEWS", "ID", "ID, JOB_ID, STUDENT_USER_ID, RATING, CONTENT, STATUS, CREATED_AT, UPDATED_AT"},
new String[]{"COMPANY_REVIEW_REPLIES", "ID", "ID, REVIEW_ID, COMPANY_ID, CONTENT, MODERATION_STATUS, VERSION, CREATED_AT, UPDATED_AT"},
new String[]{"INTERVIEW_SCORES", "ID", "ID, INTERVIEW_ID, APPLICATION_ID, COMPANY_USER_ID, STUDENT_USER_ID, STATUS, CREATED_AT, UPDATED_AT"},
new String[]{"CANDIDATE_SCORING_APPEALS", "ID", "ID, CANDIDATE_USER_ID, DIMENSION, PERIOD, STATUS, REASON, CREATED_AT, UPDATED_AT"},
new String[]{"STUDENT_REVIEW_WEIGHT_PREFERENCES", "ID", "ID, STUDENT_USER_ID, SOURCE, TEMPLATE_CODE, VERSION, CREATED_AT, UPDATED_AT"},
new String[]{"NOTIFICATIONS", "ID", "ID, USER_ID, TYPE, TITLE, IS_READ, CREATED_AT"},
new String[]{"MESSAGES", "ID", "ID, CONVERSATION_ID, SENDER_USER_ID, CONTENT, CREATED_AT"},
new String[]{"AUDIT_LOGS", "ID", "ID, USER_ID, ACTION, ENTITY_TYPE, ENTITY_ID, CREATED_AT"});
private Map<String, List<Map<String, Object>>> snapshot() {
Map<String, List<Map<String, Object>>> out = new LinkedHashMap<>();
for (String[] t : TABLES) {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
"SELECT " + t[2] + " FROM " + t[0] + " ORDER BY " + t[2]);
out.put(t[0], rows);
}
return out;
}
private static final List<String> PATHS = List.of(
"/profile/me/candidate-scoring/appeals",
"/interviews/1/score",
"/jobs/favorites",
"/admin/reviews/pending",
"/admin/review-replies/pending",
"/companies/me/job-reviews",
"/profile/me/review-weights");
private static final String DELETE_REVIEW = "/jobs/1/reviews/1";
@Test
@DisplayName("独立复验:非法身份请求前后,九张核心表逐字段零变化")
void illegalIdentityRequests_produceNoBusinessWrite() throws Exception {
Map<String, List<Map<String, Object>>> before = snapshot();
var bad = new UsernamePasswordAuthenticationToken("anonymousUser", "n/a", AuthorityUtils.NO_AUTHORITIES);
for (String path : PATHS) {
mockMvc.perform(get(path).with(authentication(bad))).andReturn();
mockMvc.perform(get(path).with(authentication(
new UsernamePasswordAuthenticationToken("not-a-number", "n/a", AuthorityUtils.NO_AUTHORITIES)))).andReturn();
}
mockMvc.perform(delete(DELETE_REVIEW).with(authentication(bad))).andReturn();
Map<String, List<Map<String, Object>>> after = snapshot();
for (String[] t : TABLES) {
assertThat(after.get(t[0]))
.as("%s 必须逐字段零变化", t[0])
.isEqualTo(before.get(t[0]));
}
}
@Test
@DisplayName("独立复验:无 Token 请求前后同样零变化")
void noTokenRequests_produceNoBusinessWrite() throws Exception {
Map<String, List<Map<String, Object>>> before = snapshot();
for (String path : PATHS) {
mockMvc.perform(get(path)).andReturn();
}
mockMvc.perform(delete(DELETE_REVIEW)).andReturn();
Map<String, List<Map<String, Object>>> after = snapshot();
for (String[] t : TABLES) {
assertThat(after.get(t[0])).as("%s", t[0]).isEqualTo(before.get(t[0]));
}
}
}