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})。 * *

覆盖四种状态、空集合、null、以及"可计算但分数缺失"的边界; * 并锁定两个入口(计算结果 / 快照)在等价输入下的产出关系。

*/ 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 results(String status, String score, int n) { List 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 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 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 producers = new java.util.ArrayList<>(); try (java.util.stream.Stream 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"); } }