package cn.lihu.jh.module.ecg.enums;
|
|
import org.junit.Test;
|
|
import java.util.ArrayList;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
|
import static org.junit.Assert.*;
|
|
/**
|
* 回归测试:排队序号规则枚举 {@link SeqRuleEnum}。
|
*
|
* <h3>背景</h3>
|
* 「签到时如何给患者排队序号」按检查项目配置,三种规则:
|
* <ol>
|
* <li>预约号 —— 用预约系统下发的号</li>
|
* <li>签到序号 —— 当天同检查类型内连续递增,<b>每 9 个号预留 1 个给加急</b></li>
|
* <li>时段号 —— 按预约时间段给号(改造前的原有行为,也是未配置时的默认)</li>
|
* </ol>
|
*
|
* <h3>本测试锁定的关键行为</h3>
|
* <ul>
|
* <li>{@code parse(null)} 回退到 {@code TIMESLOT} —— 存量数据 {@code seq_rule} 为 NULL 时
|
* 升级后**行为与改造前一致**;</li>
|
* <li>普通号序列跳过 10 的倍数(预留号),且**不跳号、不重号**;</li>
|
* <li>预留号序列 = 10 的倍数;</li>
|
* <li>两类号集合**不相交**(这是"两个独立计数器"设计的目的)。</li>
|
* </ul>
|
*
|
* <h3>曾捕获的真实缺陷(保留记录)</h3>
|
* 初版把「分配号」写成 {@code nextNormalSeq(++counter)} 并期望它在 10 时跳到 11:
|
* <pre>
|
* counter: 9 → 分配 9
|
* counter: 10 → nextNormalSeq(10) 期望 11?(错)
|
* counter: 11 → 分配 11 ← 与上一行撞车
|
* </pre>
|
* 正确语义是:**分配号 = {@code nextNormalSeq(counter)},且计数器在"下一个号是预留号"时额外 +1**,
|
* 即 {@code normal_next = normal_next + 1 + (normal_next + 1 是 10 的倍数 ? 1 : 0)}。
|
*/
|
public class SeqRuleEnumTest {
|
|
// ---------------------------------------------------------------- 解析
|
|
@Test
|
public void testParse_null_shouldFallbackToTimeslot() {
|
assertEquals(SeqRuleEnum.TIMESLOT, SeqRuleEnum.parse(null));
|
}
|
|
@Test
|
public void testParse_unknownValue_shouldFallbackToTimeslot() {
|
assertEquals(SeqRuleEnum.TIMESLOT, SeqRuleEnum.parse(99));
|
assertEquals(SeqRuleEnum.TIMESLOT, SeqRuleEnum.parse(-1));
|
}
|
|
@Test
|
public void testParse_knownValues() {
|
assertEquals(SeqRuleEnum.APPOINT, SeqRuleEnum.parse(0));
|
assertEquals(SeqRuleEnum.CHECKIN, SeqRuleEnum.parse(1));
|
assertEquals(SeqRuleEnum.TIMESLOT, SeqRuleEnum.parse(2));
|
}
|
|
// ------------------------------------------------------- 容量语义差异
|
|
@Test
|
public void testIsCapacityLimited_onlyTimeslot() {
|
assertFalse(SeqRuleEnum.APPOINT.isCapacityLimited());
|
assertFalse(SeqRuleEnum.CHECKIN.isCapacityLimited());
|
assertTrue(SeqRuleEnum.TIMESLOT.isCapacityLimited());
|
}
|
|
// ---------------------------------------------------------- 预留号判定
|
|
@Test
|
public void testIsReservedSeq() {
|
assertTrue(SeqRuleEnum.isReservedSeq(10));
|
assertTrue(SeqRuleEnum.isReservedSeq(20));
|
assertTrue(SeqRuleEnum.isReservedSeq(100));
|
assertFalse(SeqRuleEnum.isReservedSeq(1));
|
assertFalse(SeqRuleEnum.isReservedSeq(9));
|
assertFalse(SeqRuleEnum.isReservedSeq(11));
|
assertFalse(SeqRuleEnum.isReservedSeq(0));
|
assertFalse(SeqRuleEnum.isReservedSeq(-10));
|
}
|
|
@Test
|
public void testRoundSize_matchesReservedRule() {
|
assertEquals(10, SeqRuleEnum.ROUND_SIZE);
|
assertEquals(1, SeqRuleEnum.RESERVED_PER_ROUND);
|
// 「每 9 个号预留 1 个」= 9 普通 + 1 预留 = 一轮 10 个
|
assertEquals(SeqRuleEnum.ROUND_SIZE, 9 + SeqRuleEnum.RESERVED_PER_ROUND);
|
}
|
|
// -------------------------------------------------------- 普通号序列
|
|
@Test
|
public void testNextNormalSeq_shouldReturnNonReservedValue() {
|
// 直接语义:返回 from 起第一个非预留号(10 本身是预留号 → 返回 11)
|
assertEquals(1, SeqRuleEnum.nextNormalSeq(1));
|
assertEquals(9, SeqRuleEnum.nextNormalSeq(9));
|
assertEquals("10 是预留号,应跳到 11", 11, SeqRuleEnum.nextNormalSeq(10));
|
assertEquals(11, SeqRuleEnum.nextNormalSeq(11));
|
assertEquals(19, SeqRuleEnum.nextNormalSeq(19));
|
assertEquals(21, SeqRuleEnum.nextNormalSeq(20));
|
assertEquals("99 本身不是预留号,原样返回", 99, SeqRuleEnum.nextNormalSeq(99));
|
assertEquals("100 是预留号,跳到 101", 101, SeqRuleEnum.nextNormalSeq(100));
|
}
|
|
/**
|
* 端到端模拟 Mapper 的推进公式,验证**连续 30 次普通取号不重号、不跳号**。
|
*
|
* <p>公式与 {@code QueueSequenceMapper#advanceNormalSeq} 完全一致:
|
* <pre>
|
* INSERT 时 normal_next = 1
|
* UPDATE 时 normal_next = normal_next + 1 + IF((normal_next + 1) % 10 = 0, 1, 0)
|
* 分配号 = nextNormalSeq(normal_next - 上次推进量)? —— 不对,
|
* 实际是:分配号由「读回后的 normal_next」经 nextNormalSeq 得到
|
* </pre>
|
* 这里用最直白的方式还原:维护 {@code lastAssigned},每次
|
* {@code next = lastAssigned + 1},若是预留号则 {@code next++}(跳过),
|
* 然后 {@code lastAssigned = next}。
|
*/
|
@Test
|
public void testNormalSequence_isContiguousAndSkipsReserved() {
|
List<Integer> assigned = new ArrayList<>();
|
int lastAssigned = 0;
|
for (int i = 0; i < 30; i++) {
|
int next = lastAssigned + 1;
|
next = SeqRuleEnum.nextNormalSeq(next); // 跳过预留号
|
assigned.add(next);
|
lastAssigned = next;
|
}
|
|
// 期望:1..9, 11..19, 21..29, 31..39 的前 30 个
|
int[] expected = {
|
1, 2, 3, 4, 5, 6, 7, 8, 9,
|
11, 12, 13, 14, 15, 16, 17, 18, 19,
|
21, 22, 23, 24, 25, 26, 27, 28, 29,
|
31, 32, 33
|
};
|
for (int i = 0; i < expected.length; i++) {
|
assertEquals("第 " + (i + 1) + " 个普通号", expected[i], (int) assigned.get(i));
|
}
|
|
// 无重号
|
Set<Integer> uniq = new HashSet<>(assigned);
|
assertEquals("普通号不得重复", assigned.size(), uniq.size());
|
|
// 无 10 的倍数
|
for (Integer v : assigned) {
|
assertFalse("普通号不应落在预留号上: " + v, SeqRuleEnum.isReservedSeq(v));
|
}
|
}
|
|
@Test
|
public void testNextNormalSeq_withZeroOrNegative_shouldStartFromOne() {
|
assertEquals(1, SeqRuleEnum.nextNormalSeq(0));
|
assertEquals(1, SeqRuleEnum.nextNormalSeq(-5));
|
}
|
|
// -------------------------------------------------------- 预留号序列
|
|
@Test
|
public void testNextReservedSeq_shouldLandOnMultiplesOfTen() {
|
assertEquals(10, SeqRuleEnum.nextReservedSeq(1));
|
assertEquals(10, SeqRuleEnum.nextReservedSeq(9));
|
assertEquals("已是预留号则原样返回", 10, SeqRuleEnum.nextReservedSeq(10));
|
assertEquals(20, SeqRuleEnum.nextReservedSeq(11));
|
assertEquals(20, SeqRuleEnum.nextReservedSeq(19));
|
assertEquals(20, SeqRuleEnum.nextReservedSeq(20));
|
assertEquals(30, SeqRuleEnum.nextReservedSeq(21));
|
}
|
|
@Test
|
public void testNextReservedSeq_withZeroOrNegative() {
|
assertEquals(10, SeqRuleEnum.nextReservedSeq(0));
|
assertEquals(10, SeqRuleEnum.nextReservedSeq(-3));
|
}
|
|
// ------------------------------------------------- 两类号不相交(核心)
|
|
@Test
|
public void testNormalAndReservedSequences_shouldNotIntersect() {
|
// 模拟:100 次普通 + 10 次加急
|
Set<Integer> normalSet = new HashSet<>();
|
int last = 0;
|
for (int i = 0; i < 100; i++) {
|
last = SeqRuleEnum.nextNormalSeq(last + 1);
|
normalSet.add(last);
|
}
|
|
Set<Integer> reservedSet = new HashSet<>();
|
int reservedNext = 10;
|
for (int i = 0; i < 10; i++) {
|
reservedSet.add(SeqRuleEnum.nextReservedSeq(reservedNext));
|
reservedNext += SeqRuleEnum.ROUND_SIZE;
|
}
|
|
assertEquals("普通号不得重复", 100, normalSet.size());
|
assertEquals("预留号不得重复", 10, reservedSet.size());
|
|
Set<Integer> intersection = new HashSet<>(normalSet);
|
intersection.retainAll(reservedSet);
|
assertTrue("普通号与预留号集合不得有交集,实际交集=" + intersection, intersection.isEmpty());
|
}
|
|
/**
|
* 反证:为什么必须"两个独立计数器"。
|
*
|
* <p>若只用一个递增计数器、并把"跳过预留号"后的值直接当号码用,
|
* 会在 counter=10 与 counter=11 两次分配中得到同一个号 11。
|
*/
|
@Test
|
public void testSingleCounterNaiveApproach_wouldCollide_documentingWhyTwoCounters() {
|
assertEquals("counter=10 时跳过预留号得到 11", 11, SeqRuleEnum.nextNormalSeq(10));
|
assertEquals("counter=11 时直接得到 11", 11, SeqRuleEnum.nextNormalSeq(11));
|
assertEquals("两者相同 → 朴素做法必然重号",
|
SeqRuleEnum.nextNormalSeq(10), SeqRuleEnum.nextNormalSeq(11));
|
}
|
|
// ------------------------------------------------------------- 数组
|
|
@Test
|
public void testArrays_containsAllRules() {
|
int[] arrays = SeqRuleEnum.ARRAYS;
|
assertEquals(3, arrays.length);
|
assertEquals(0, arrays[0]);
|
assertEquals(1, arrays[1]);
|
assertEquals(2, arrays[2]);
|
}
|
}
|