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
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]));
|
|
}
|
|
}
|
|
}
|
|
|