|
|
|
|
@ -0,0 +1,291 @@
|
|
|
|
|
package com.yau.digitalrmb.training.score.interfaces.rest;
|
|
|
|
|
|
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
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.http.MediaType;
|
|
|
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
import org.springframework.test.context.ActiveProfiles;
|
|
|
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
|
|
|
import org.springframework.test.web.servlet.request.RequestPostProcessor;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
|
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
|
|
|
|
|
|
@SpringBootTest
|
|
|
|
|
@AutoConfigureMockMvc
|
|
|
|
|
@ActiveProfiles("test")
|
|
|
|
|
class TrainingScoreControllerTest {
|
|
|
|
|
private static final String USER = "00000000-0000-0000-0000-000000000931";
|
|
|
|
|
private static final String OTHER_USER = "00000000-0000-0000-0000-000000000932";
|
|
|
|
|
private static final String SCHOOL = "1001";
|
|
|
|
|
private static final String OTHER_SCHOOL = "1002";
|
|
|
|
|
/** 成绩表模块名(模块一:中国人民银行生成数字货币)。 */
|
|
|
|
|
private static final String MODULE_ONE = "中国人民银行生成数字货币";
|
|
|
|
|
/** 成绩表模块名(模块三:开通数字货币钱包)。 */
|
|
|
|
|
private static final String MODULE_THREE = "开通数字货币钱包";
|
|
|
|
|
|
|
|
|
|
@Autowired private MockMvc mockMvc;
|
|
|
|
|
@Autowired private JdbcTemplate jdbc;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void seed() {
|
|
|
|
|
clean();
|
|
|
|
|
insertUser(USER, "score-a", SCHOOL);
|
|
|
|
|
insertUser(OTHER_USER, "score-b", OTHER_SCHOOL);
|
|
|
|
|
// 模块一:2 条有效 + 1 条已删除 → 操作次数 3,当前操作次数 2
|
|
|
|
|
insertScore("score-a-1", USER, MODULE_ONE, 1, 98.00, false);
|
|
|
|
|
insertScore("score-a-2", USER, MODULE_ONE, 2, 52.00, false);
|
|
|
|
|
insertScore("score-a-3", USER, MODULE_ONE, 3, 30.00, true);
|
|
|
|
|
// 模块三:1 条有效
|
|
|
|
|
insertScore("score-a-4", USER, MODULE_THREE, 3, 70.00, false);
|
|
|
|
|
// 其它用户的模块一数据,不应出现在当前用户结果中
|
|
|
|
|
insertScore("score-b-1", OTHER_USER, MODULE_ONE, 1, 55.00, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
void tearDown() {
|
|
|
|
|
clean();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void returnsSixModulesWithDefaultsWhenNoConfig() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(200))
|
|
|
|
|
.andExpect(jsonPath("$.data.length()").value(6))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].mouduleCode").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].moudule").value("中国人民银行生成数字货币"))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].scoringCriteria").value("选错/填错一个扣 0.5 分"))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].moduleWeight").value(0.30))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].operationCount").value(3))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].currentCount").value(2))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].maxScore").value(98.00))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].minScore").value(30.00))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].avgScore").value(60.00))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].details.length()").value(2))
|
|
|
|
|
.andExpect(jsonPath("$.data[1].moduleWeight").value(0.25))
|
|
|
|
|
.andExpect(jsonPath("$.data[1].operationCount").value(0))
|
|
|
|
|
.andExpect(jsonPath("$.data[1].details.length()").value(0))
|
|
|
|
|
.andExpect(jsonPath("$.data[5].mouduleCode").value(6))
|
|
|
|
|
.andExpect(jsonPath("$.data[5].moduleWeight").value(0.15));
|
|
|
|
|
// 无配置时成绩查询本身应为本校落库一条默认模块权重配置(含默认扣分)
|
|
|
|
|
assertThat(countModuleWeight(SCHOOL)).isEqualTo(1);
|
|
|
|
|
assertThat(jdbc.queryForObject(
|
|
|
|
|
"SELECT deduct_score FROM training_module_weight WHERE school_id=?", BigDecimal.class, SCHOOL))
|
|
|
|
|
.isEqualByComparingTo("0.50");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void returnsSingleModuleWhenModuleProvided() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores")
|
|
|
|
|
.param("moudule", "开通数字货币钱包").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.length()").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].mouduleCode").value(3))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].operationCount").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].currentCount").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].maxScore").value(70.00))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].details[0].id").value("score-a-4"))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].details[0].userId").value(USER));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void hidesOtherUserScoresAndDeletedDetails() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores").with(otherUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data[0].operationCount").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].currentCount").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].details[0].id").value("score-b-1"));
|
|
|
|
|
assertThat(jdbc.queryForObject(
|
|
|
|
|
"SELECT COUNT(*) FROM stu_module_score_details WHERE user_id=? AND moudule=?",
|
|
|
|
|
Integer.class, USER, MODULE_ONE)).isEqualTo(3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void usesTeacherModuleWeightAndDeductScore() throws Exception {
|
|
|
|
|
insertModuleWeight(SCHOOL, 0.50, 0.25, 0.05, 0.05, 0.05, 0.10, 1);
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data[0].scoringCriteria").value("选错/填错一个扣 1 分"))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].moduleWeight").value(0.50))
|
|
|
|
|
.andExpect(jsonPath("$.data[1].moduleWeight").value(0.25));
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores").with(otherUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data[0].scoringCriteria").value("选错/填错一个扣 0.5 分"))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].moduleWeight").value(0.30));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void firstQuerySeedsDefaultSchoolWeightThenUpdatesById() throws Exception {
|
|
|
|
|
// 首次查询:库中无数据 → 落库一条默认配置
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-weights/school").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.id").isNotEmpty())
|
|
|
|
|
.andExpect(jsonPath("$.data.trainingWeight").value(0.80))
|
|
|
|
|
.andExpect(jsonPath("$.data.reportWeight").value(0.20))
|
|
|
|
|
.andExpect(jsonPath("$.data.configured").value(false));
|
|
|
|
|
assertThat(countSchoolWeight(SCHOOL)).isEqualTo(1);
|
|
|
|
|
|
|
|
|
|
String schoolWeightId = jdbc.queryForObject(
|
|
|
|
|
"SELECT id FROM training_school_score_weight WHERE school_id=?", String.class, SCHOOL);
|
|
|
|
|
|
|
|
|
|
// 再次查询:命中已有配置
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-weights/school").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.id").value(schoolWeightId))
|
|
|
|
|
.andExpect(jsonPath("$.data.configured").value(true));
|
|
|
|
|
|
|
|
|
|
// 按 id 修改
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/school").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"id\":\"" + schoolWeightId + "\",\"trainingWeight\":0.70,"
|
|
|
|
|
+ "\"reportWeight\":0.30}"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.trainingWeight").value(0.70))
|
|
|
|
|
.andExpect(jsonPath("$.data.reportWeight").value(0.30))
|
|
|
|
|
.andExpect(jsonPath("$.data.configured").value(true));
|
|
|
|
|
|
|
|
|
|
// 两次修改后仍只有一条记录
|
|
|
|
|
assertThat(countSchoolWeight(SCHOOL)).isEqualTo(1);
|
|
|
|
|
|
|
|
|
|
// 权重和不等于 1 → 400
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/school").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"id\":\"" + schoolWeightId + "\",\"trainingWeight\":0.70,"
|
|
|
|
|
+ "\"reportWeight\":0.20}"))
|
|
|
|
|
.andExpect(status().isBadRequest())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(400));
|
|
|
|
|
|
|
|
|
|
// 改别的学校的记录 → 403
|
|
|
|
|
jdbc.update("INSERT INTO training_school_score_weight (id,school_id,training_weight,report_weight,"
|
|
|
|
|
+ "created_at,updated_at,deleted) VALUES ('w-other',?,0.50,0.50,CURRENT_TIMESTAMP,"
|
|
|
|
|
+ "CURRENT_TIMESTAMP,FALSE)", OTHER_SCHOOL);
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/school").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"id\":\"w-other\",\"trainingWeight\":0.70,\"reportWeight\":0.30}"))
|
|
|
|
|
.andExpect(status().isForbidden())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(403));
|
|
|
|
|
|
|
|
|
|
// id 不存在 → 404
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/school").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
|
|
|
.content("{\"id\":\"not-exist\",\"trainingWeight\":0.70,\"reportWeight\":0.30}"))
|
|
|
|
|
.andExpect(status().isNotFound())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(404));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void firstQuerySeedsDefaultModuleWeightsThenUpdatesById() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-weights/modules").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.id").isNotEmpty())
|
|
|
|
|
.andExpect(jsonPath("$.data.deductScore").value(0.50))
|
|
|
|
|
.andExpect(jsonPath("$.data.configured").value(false))
|
|
|
|
|
.andExpect(jsonPath("$.data.modules.length()").value(6))
|
|
|
|
|
.andExpect(jsonPath("$.data.modules[0].mouduleCode").value(1))
|
|
|
|
|
.andExpect(jsonPath("$.data.modules[0].weight").value(0.30));
|
|
|
|
|
assertThat(countModuleWeight(SCHOOL)).isEqualTo(1);
|
|
|
|
|
|
|
|
|
|
String moduleWeightId = jdbc.queryForObject(
|
|
|
|
|
"SELECT id FROM training_module_weight WHERE school_id=?", String.class, SCHOOL);
|
|
|
|
|
|
|
|
|
|
String valid = "{\"id\":\"" + moduleWeightId + "\",\"deductScore\":0.25,\"modules\":["
|
|
|
|
|
+ "{\"moudule\":\"中国人民银行生成数字货币\",\"mouduleCode\":1,\"weight\":0.20},"
|
|
|
|
|
+ "{\"moudule\":\"中国人民银行发行数字货币\",\"mouduleCode\":2,\"weight\":0.20},"
|
|
|
|
|
+ "{\"moudule\":\"开通数字货币钱包\",\"mouduleCode\":3,\"weight\":0.15},"
|
|
|
|
|
+ "{\"moudule\":\"用户兑换数字货币\",\"mouduleCode\":4,\"weight\":0.15},"
|
|
|
|
|
+ "{\"moudule\":\"用户支付数字货币\",\"mouduleCode\":5,\"weight\":0.15},"
|
|
|
|
|
+ "{\"moudule\":\"数字人民币创新应用场景仿真\",\"mouduleCode\":6,\"weight\":0.15}]}";
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/modules").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content(valid))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data.deductScore").value(0.25))
|
|
|
|
|
.andExpect(jsonPath("$.data.configured").value(true))
|
|
|
|
|
.andExpect(jsonPath("$.data.modules[0].weight").value(0.20))
|
|
|
|
|
.andExpect(jsonPath("$.data.modules[1].weight").value(0.20));
|
|
|
|
|
assertThat(countModuleWeight(SCHOOL)).isEqualTo(1);
|
|
|
|
|
|
|
|
|
|
String invalidSum = valid.replace("\"mouduleCode\":6,\"weight\":0.15", "\"mouduleCode\":6,\"weight\":0.10");
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/modules").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content(invalidSum))
|
|
|
|
|
.andExpect(status().isBadRequest())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(400));
|
|
|
|
|
|
|
|
|
|
String invalidName = valid.replace("\"moudule\":\"用户支付数字货币\"", "\"moudule\":\"支付模块\"");
|
|
|
|
|
mockMvc.perform(post("/api/v1/training-weights/modules").with(currentUser())
|
|
|
|
|
.contentType(MediaType.APPLICATION_JSON).content(invalidName))
|
|
|
|
|
.andExpect(status().isBadRequest())
|
|
|
|
|
.andExpect(jsonPath("$.code").value(400));
|
|
|
|
|
|
|
|
|
|
// 学生端成绩查询读到新权重与新扣分
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores").with(currentUser()))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(jsonPath("$.data[0].moduleWeight").value(0.20))
|
|
|
|
|
.andExpect(jsonPath("$.data[0].scoringCriteria").value("选错/填错一个扣 0.25 分"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void rejectsAnonymousAccess() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-scores")).andExpect(status().isUnauthorized());
|
|
|
|
|
mockMvc.perform(get("/api/v1/training-weights/school")).andExpect(status().isUnauthorized());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RequestPostProcessor currentUser() {
|
|
|
|
|
return jwt().jwt(token -> token.subject(USER).claim("userId", USER)
|
|
|
|
|
.claim("preferred_username", "score-a"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private RequestPostProcessor otherUser() {
|
|
|
|
|
return jwt().jwt(token -> token.subject(OTHER_USER).claim("userId", OTHER_USER)
|
|
|
|
|
.claim("preferred_username", "score-b"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void insertUser(String id, String account, String schoolId) {
|
|
|
|
|
jdbc.update("INSERT INTO sys_user (user_id,student_id,password,user_name,class_id,class_name,school_id,"
|
|
|
|
|
+ "school_name,role_id,is_deleted,zy_user_id) VALUES (?,?,?,?,?,'成绩测试班',?,'延安大学',4,0,?)",
|
|
|
|
|
id, account, "unused", account, "2001", schoolId, account);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void insertScore(String id, String userId, String module, int serialNumber, double score,
|
|
|
|
|
boolean deleted) {
|
|
|
|
|
jdbc.update("INSERT INTO stu_module_score_details (id,moudule,learning_projects,assessment_items,"
|
|
|
|
|
+ "scoring_criteria,completion_status,score_project,user_id,total_score,serial_number,schedule,deleted) "
|
|
|
|
|
+ "VALUES (?,?,'实验实训','错误次数','0.5','1',?,?,100.00,?,?,?)",
|
|
|
|
|
id, module, score, userId, serialNumber, 50.00, deleted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int countSchoolWeight(String schoolId) {
|
|
|
|
|
return jdbc.queryForObject("SELECT COUNT(*) FROM training_school_score_weight WHERE school_id=?",
|
|
|
|
|
Integer.class, schoolId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int countModuleWeight(String schoolId) {
|
|
|
|
|
return jdbc.queryForObject("SELECT COUNT(*) FROM training_module_weight WHERE school_id=?",
|
|
|
|
|
Integer.class, schoolId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void insertModuleWeight(String schoolId, double w1, double w2, double w3, double w4, double w5,
|
|
|
|
|
double w6, double deductScore) {
|
|
|
|
|
jdbc.update("INSERT INTO training_module_weight (id,school_id,module1_weight,module2_weight,"
|
|
|
|
|
+ "module3_weight,module4_weight,module5_weight,module6_weight,deduct_score,created_at,"
|
|
|
|
|
+ "updated_at,deleted) VALUES ('w-2',?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,FALSE)",
|
|
|
|
|
schoolId, w1, w2, w3, w4, w5, w6, deductScore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void clean() {
|
|
|
|
|
jdbc.update("DELETE FROM stu_module_score_details WHERE user_id IN (?,?)", USER, OTHER_USER);
|
|
|
|
|
jdbc.update("DELETE FROM training_module_weight WHERE school_id IN (?,?)", SCHOOL, OTHER_SCHOOL);
|
|
|
|
|
jdbc.update("DELETE FROM training_school_score_weight WHERE school_id IN (?,?)", SCHOOL, OTHER_SCHOOL);
|
|
|
|
|
jdbc.update("DELETE FROM sys_user WHERE user_id IN (?,?)", USER, OTHER_USER);
|
|
|
|
|
}
|
|
|
|
|
}
|