forked from baskbuddy/zhangshun
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.
141 lines
6.8 KiB
141 lines
6.8 KiB
package com.mydreamjob.common.r01c;
|
|
|
|
import com.mydreamjob.candidatescore.CandidateDimensionScore;
|
|
import com.mydreamjob.candidatescore.CandidateOverviewStatusPolicy;
|
|
import com.mydreamjob.candidatescore.CandidateScoringCalculator;
|
|
import org.junit.jupiter.api.DisplayName;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
|
/**
|
|
* R01-C 特征测试:overviewStatus 聚合规则(唯一实现 {@code CandidateOverviewStatusPolicy})。
|
|
*
|
|
* <p>覆盖四种状态、空集合、null、以及"可计算但分数缺失"的边界;
|
|
* 并锁定两个入口(计算结果 / 快照)在等价输入下的产出关系。</p>
|
|
*/
|
|
class OverviewStatusPolicyCharacterizationTest {
|
|
|
|
private static final int EXPECTED = 8;
|
|
|
|
private static CandidateScoringCalculator.DimensionResult result(String status, String score) {
|
|
CandidateScoringCalculator.DimensionResult r =
|
|
new CandidateScoringCalculator.DimensionResult("EDUCATION");
|
|
r.status = status;
|
|
r.score = score == null ? null : new BigDecimal(score);
|
|
return r;
|
|
}
|
|
|
|
private static List<CandidateScoringCalculator.DimensionResult> results(String status, String score, int n) {
|
|
List<CandidateScoringCalculator.DimensionResult> list = new ArrayList<>();
|
|
for (int i = 0; i < n; i++) list.add(result(status, score));
|
|
return list;
|
|
}
|
|
|
|
private static CandidateDimensionScore snapshot(String status) {
|
|
CandidateDimensionScore s = new CandidateDimensionScore();
|
|
s.setStatus(status);
|
|
return s;
|
|
}
|
|
|
|
// ── 计算结果入口 ──────────────────────────────────────────
|
|
|
|
@Test
|
|
@DisplayName("特征:8/8 可计算 → OK;7/8 → PARTIAL;0/8 且全 PENDING → PENDING;否则 INSUFFICIENT_DATA")
|
|
void computedResultsFourStates() {
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("OK", "80", 8), EXPECTED))
|
|
.isEqualTo("OK");
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("ESTIMATED", "80", 8), EXPECTED))
|
|
.as("ESTIMATED 与 OK 同为可计算")
|
|
.isEqualTo("OK");
|
|
|
|
List<CandidateScoringCalculator.DimensionResult> partial = results("OK", "80", 7);
|
|
partial.add(result("PENDING", null));
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(partial, EXPECTED)).isEqualTo("PARTIAL");
|
|
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("PENDING", null, 8), EXPECTED))
|
|
.isEqualTo("PENDING");
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("INSUFFICIENT_DATA", null, 8), EXPECTED))
|
|
.isEqualTo("INSUFFICIENT_DATA");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("特征:OK 但分数缺失 → 不可计算(status 与 score 都必须满足)")
|
|
void okStatusWithoutScoreIsNotComputable() {
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("OK", null, 8), EXPECTED))
|
|
.isEqualTo("INSUFFICIENT_DATA");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("特征:空列表 → PENDING(计算结果入口的 empty 语义)")
|
|
void emptyComputedResultsIsPending() {
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(List.of(), EXPECTED)).isEqualTo("PENDING");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("特征:null 结果列表 → NullPointerException(既有契约)")
|
|
void nullComputedResultsThrows() {
|
|
assertThatThrownBy(() -> CandidateOverviewStatusPolicy.fromComputedResults(null, EXPECTED))
|
|
.isInstanceOf(NullPointerException.class);
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("特征:expectedDimensions 大于实际可计算数时不得判 OK")
|
|
void expectedDimensionsGatesOk() {
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(results("OK", "80", 8), 9))
|
|
.isEqualTo("PARTIAL");
|
|
}
|
|
|
|
@Test
|
|
@DisplayName("特征:PENDING 与 INSUFFICIENT_DATA 混合 → INSUFFICIENT_DATA(非 PENDING)")
|
|
void mixedPendingAndInsufficientIsInsufficient() {
|
|
List<CandidateScoringCalculator.DimensionResult> mixed = new ArrayList<>();
|
|
mixed.add(result("PENDING", null));
|
|
mixed.add(result("INSUFFICIENT_DATA", null));
|
|
assertThat(CandidateOverviewStatusPolicy.fromComputedResults(mixed, EXPECTED))
|
|
.isEqualTo("INSUFFICIENT_DATA");
|
|
}
|
|
|
|
// ── 快照入口 ──────────────────────────────────────────────
|
|
|
|
@Test
|
|
@DisplayName("特征:快照入口 —— OK/ESTIMATED 全可计算 → OK;空/null → INSUFFICIENT_DATA")
|
|
void snapshotEntryStates() {
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(
|
|
List.of(snapshot("OK"), snapshot("ESTIMATED")))).isEqualTo("OK");
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(
|
|
List.of(snapshot("OK"), snapshot("PENDING")))).isEqualTo("PARTIAL");
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(
|
|
List.of(snapshot("PENDING"), snapshot("PENDING")))).isEqualTo("PENDING");
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(
|
|
List.of(snapshot("INSUFFICIENT_DATA"), snapshot("PENDING")))).isEqualTo("INSUFFICIENT_DATA");
|
|
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(List.of())).isEqualTo("INSUFFICIENT_DATA");
|
|
assertThat(CandidateOverviewStatusPolicy.fromSnapshots(null)).isEqualTo("INSUFFICIENT_DATA");
|
|
}
|
|
|
|
// ── 唯一实现门禁 ──────────────────────────────────────────
|
|
|
|
@Test
|
|
@DisplayName("结构门禁:overviewStatus 规则只有唯一的产出点(\"PARTIAL\" 只在该策略类出现)")
|
|
void partialStatusHasSingleProducer() throws Exception {
|
|
java.nio.file.Path root = java.nio.file.Path.of("src/main/java/com/mydreamjob");
|
|
java.util.List<String> producers = new java.util.ArrayList<>();
|
|
try (java.util.stream.Stream<java.nio.file.Path> walk = java.nio.file.Files.walk(root)) {
|
|
for (java.nio.file.Path p : walk.filter(f -> f.toString().endsWith(".java")).toList()) {
|
|
String src = java.nio.file.Files.readString(p);
|
|
if (src.contains("\"PARTIAL\"")) {
|
|
producers.add(p.getFileName().toString());
|
|
}
|
|
}
|
|
}
|
|
assertThat(producers)
|
|
.as("PARTIAL 只能由 CandidateOverviewStatusPolicy 产出")
|
|
.containsExactly("CandidateOverviewStatusPolicy.java");
|
|
}
|
|
}
|
|
|