|
|
@@ -0,0 +1,354 @@
|
|
|
+package com.etotem.cfc.unit;
|
|
|
+
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.lang.reflect.Method;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+import static org.junit.jupiter.api.Assertions.fail;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 契约级单测:团队分润定时任务(能力键 {@code team.profit.cron},单元 team)。
|
|
|
+ *
|
|
|
+ * <p><b>需求/设计锚点(规格 v0.7)</b></p>
|
|
|
+ * <ul>
|
|
|
+ * <li>需求:{@code docs/superpowers/specs/2026-11-07-referral-team-profit-sharing.md}
|
|
|
+ * §12-T4(冷静期结算、月度星级重评含升级双门槛校验、退款冲正)</li>
|
|
|
+ * <li>设计:§8.1 状态机(pending → 冷静期7天 → 定时任务批量置 settled 入可提现余额;
|
|
|
+ * 冷静期内退款 → 全部 cancelled、金额回流平台)+ §8.2 冷静期与退款(7天退款全退、
|
|
|
+ * 7天后自动 settled)+ §4.3 升级双门槛(业绩分 ≥ 目标档门槛 且 滚动12个月团队直推 ≥1,
|
|
|
+ * 见 §11-#15 拍板;月度重评落 {@code team_star_snapshot} 快照)
|
|
|
+ * + §9 边界 #7(退款跨冷静期 → 已 settled 需冲正)</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p><b>被校验的契约行为</b></p>
|
|
|
+ * <ul>
|
|
|
+ * <li>冷静期结算(§8.1/§8.2):冷静期默认 <b>7 天</b>;支付满 7 天 → 定时任务批量
|
|
|
+ * pending→settled 入可提现余额;冷静期内退款 → team_share_record / team_award_record
|
|
|
+ * 全部 cancelled、资金不发放、金额回流平台;退款跨冷静期(已 settled)→ 冲正
|
|
|
+ * (负数记录或 cancelled + 余额扣减,§9#7)</li>
|
|
|
+ * <li>月度星级重评(§4.3 + T4):每月 1 日 00:30 定时重评(cron
|
|
|
+ * {@code 0 30 0 1 * ?}),落 {@code team_star_snapshot}(period=YYYYMM,
|
|
|
+ * uk_user_period 唯一键)</li>
|
|
|
+ * <li>升级双门槛(§4.3 + #15 拍板):升级必须「业绩分 ≥ 目标档门槛 且 本窗口团队直推 ≥1」
|
|
|
+ * 双条件同时满足;缺任一项 → 不能升级(维持当前档);滚动窗口滑出业绩分下降 →
|
|
|
+ * 自动降级(不保留历史级别、不固化)</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p><b>宿主契约(TDD-RED 锚点)</b></p>
|
|
|
+ * <ul>
|
|
|
+ * <li>{@code com.etotem.cfc.task.TeamProfitCronTask} —— §12-T4 定时任务宿主(依赖现有
|
|
|
+ * {@code task/} 包 {@code XxxScheduledTask} 命名约定),2026-11-08 <b>尚未实现</b>;
|
|
|
+ * 承载冷静期结算、月度星级重评、退款冲正三个批次入口</li>
|
|
|
+ * <li>{@code com.etotem.cfc.service.StarScoreCalculator} —— §4.3「计算组件」宿主,
|
|
|
+ * 承载档位映射与升级双门槛判定({@code isPromotable}),<b>尚未实现</b></li>
|
|
|
+ * </ul>
|
|
|
+ * <p>两个宿主均不存在 → 本测试通过反射锚定类名(避免直接 import 不存在类导致编译失败),
|
|
|
+ * 运行时 {@code Class.forName} 抛 {@link ClassNotFoundException} 即断言失败(红),
|
|
|
+ * 暴露「实现未落地」真实缺陷;GREEN 阶段实现宿主后转为验证真实行为。</p>
|
|
|
+ *
|
|
|
+ * <p><b>契约方法签名(实现方按此落地)</b></p>
|
|
|
+ * <ul>
|
|
|
+ * <li>{@code TeamProfitCronTask#int coolingPeriodDays()} —— 冷静期天数(默认 7)</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#boolean isWithinCoolingPeriod(long paidAtEpochMs, long nowEpochMs)}
|
|
|
+ * —— 支付时刻距今是否仍在冷静期内(now < paidAt+7d)</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#int settleDuePendingRecords()} —— 批量:冷静期满
|
|
|
+ * pending→settled 入可提现余额,返回处理条数</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#int cancelTeamRecordsForRefund(long orderId)} —— 冷静期内
|
|
|
+ * 退款:team_share_record/team_award_record 全部 cancelled,资金不发放,返回取消条数</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#boolean reverseSettledBalanceForCrossCoolingRefund(long orderId)}
|
|
|
+ * —— 退款跨冷静期:已 settled 记录冲正(负数记录或 cancelled + 余额扣减,§9#7)</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#long refundRefluxAmountCent(long orderId)} —— 取消/冲正后
|
|
|
+ * 金额回流平台(分,≥0)</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#String monthlyStarReevalCron()} —— 月度重评 cron 表达式
|
|
|
+ * (每月 1 日 00:30:{@code 0 30 0 1 * ?})</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#String snapshotPeriod(int year, int month)} —— 快照周期
|
|
|
+ * YYYYMM(如 2026-11 → {@code "202611"},对应 §7.6 uk_user_period)</li>
|
|
|
+ * <li>{@code TeamProfitCronTask#int snapshotMonthlyStar(long userId, int year, int month)}
|
|
|
+ * —— 月度重评落快照,返回本期星级(1-6)</li>
|
|
|
+ * <li>{@code StarScoreCalculator#int thresholdOfStar(int star)} —— 星级业绩分门槛
|
|
|
+ * (0 / 8000 / 25000 / 70000 / 140000 / 250000,§4.3 推荐校准值)</li>
|
|
|
+ * <li>{@code StarScoreCalculator#int starForScore(long score)} —— 业绩分 → 星级档位映射
|
|
|
+ * (入团即一星)</li>
|
|
|
+ * <li>{@code StarScoreCalculator#boolean isPromotable(long score, int directValidNewRefs, int targetStar)}
|
|
|
+ * —— 升级双门槛:score ≥ 目标档门槛 且 团队直推 ≥1(§4.3/#15 拍板)</li>
|
|
|
+ * <li>{@code StarScoreCalculator#int reEvaluateStar(long score, int directValidNewRefs, int currentStar)}
|
|
|
+ * —— 重评:升星须过双门槛;降星自动(滚动窗口滑出,不保留历史级别)</li>
|
|
|
+ * </ul>
|
|
|
+ *
|
|
|
+ * <p><b>测试范围</b>:纯契约级单测,无 Spring / 无 DB / 无外部服务依赖(纯反射 + 纯函数校验)。</p>
|
|
|
+ * <p><b>权限流</b>:定时任务在服务端后台按调度执行,无用户侧角色鉴权分支,故权限流不适用。</p>
|
|
|
+ *
|
|
|
+ * <p><b>RED 预期</b>:宿主 {@code TeamProfitCronTask} 与 {@code StarScoreCalculator} 均未实现,
|
|
|
+ * 全部用例在反射探测处失败(红),断言原文为「契约宿主 … 未实现」。</p>
|
|
|
+ */
|
|
|
+class TeamProfitCronContractTest {
|
|
|
+
|
|
|
+ /** 规格设计宿主类 1:T4 定时任务(§12-T4,未实现)。 */
|
|
|
+ private static final String HOST_CRON_TASK = "com.etotem.cfc.task.TeamProfitCronTask";
|
|
|
+
|
|
|
+ /** 规格设计宿主类 2:星级计算组件(§4.3,未实现)。 */
|
|
|
+ private static final String HOST_STAR_CALC = "com.etotem.cfc.service.StarScoreCalculator";
|
|
|
+
|
|
|
+ /** 里程碑常量:冷静期 7 天(§8.2,与退款窗口对齐)。 */
|
|
|
+ private static final int COOLING_PERIOD_DAYS = 7;
|
|
|
+
|
|
|
+ private static final long DAY_MS = 24L * 60 * 60 * 1000;
|
|
|
+
|
|
|
+ // ==================== §8.1/§8.2 冷静期常量与判定 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void coolingPeriod_defaultDays_isSeven() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ assertEquals(COOLING_PERIOD_DAYS, callInt(host, "coolingPeriodDays"),
|
|
|
+ "冷静期必须为 7 天(§8.2,与退款窗口对齐)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void coolingPeriod_boundary_day6_stillInside() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ long paidAt = 1_700_000_000_000L;
|
|
|
+ boolean inside = callBoolean(host, "isWithinCoolingPeriod", paidAt, paidAt + 6 * DAY_MS);
|
|
|
+ assertTrue(inside, "支付后第 6 天仍在冷静期内(7 天内退款需全部取消)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void coolingPeriod_boundary_day7_settledDue() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ long paidAt = 1_700_000_000_000L;
|
|
|
+ boolean inside = callBoolean(host, "isWithinCoolingPeriod", paidAt, paidAt + COOLING_PERIOD_DAYS * DAY_MS);
|
|
|
+ assertFalse(inside, "支付满 7 天整即视为冷静期满(定时任务可批量置 settled)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void coolingPeriod_boundary_day8_outside() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ long paidAt = 1_700_000_000_000L;
|
|
|
+ boolean inside = callBoolean(host, "isWithinCoolingPeriod", paidAt, paidAt + 8 * DAY_MS);
|
|
|
+ assertFalse(inside, "支付后第 8 天已过冷静期");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §8.1 冷静期满 → 批量结算入可提现余额 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void settleDuePending_batchSettlesToWithdrawableBalance() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ int processed = callInt(host, "settleDuePendingRecords");
|
|
|
+ assertTrue(processed >= 0,
|
|
|
+ "定时任务应批量将冷静期满的 pending 记录置 settled,入「可提现余额」(§8.1),processed=" + processed);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §8.2 / §9#7 退款冲正 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundWithinCooling_cancelAllRecordsFundsNotPaid() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ int cancelled = callInt(host, "cancelTeamRecordsForRefund", 90001L);
|
|
|
+ assertTrue(cancelled >= 1,
|
|
|
+ "冷静期内订单退款:对应 team_share_record / team_award_record 全部 cancelled、资金不发放(§8.2),cancelled=" + cancelled);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundCrossCooling_settledBalanceReversed() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ assertTrue(callBoolean(host, "reverseSettledBalanceForCrossCoolingRefund", 90002L),
|
|
|
+ "退款跨冷静期(已 settled):必须冲正——负数记录或 cancelled + 余额扣减(§9 边界 #7)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void refundReflux_amountReflowsToPlatform() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ long reflux = callLong(host, "refundRefluxAmountCent", 90001L);
|
|
|
+ assertTrue(reflux >= 0,
|
|
|
+ "取消/冲正后分润金额必须回流平台(§8.1 金额回流平台、不私发),reflux=" + reflux);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §4.3 月度星级重评:调度与快照 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void monthlyStarReeval_cronExpr_firstDay0030() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ assertEquals("0 30 0 1 * ?", callString(host, "monthlyStarReevalCron"),
|
|
|
+ "星级重评定时任务必须为每月 1 日 00:30(§4.3 重评周期)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void snapshotPeriod_formatYYYYMM() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ assertEquals("202611", callString(host, "snapshotPeriod", 2026, 11),
|
|
|
+ "快照周期必须为 YYYYMM(§7.6 team_star_snapshot.period,uk_user_period)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void snapshotMonthlyStar_writesSnapshotInRange1to6() {
|
|
|
+ Object host = contractHost(HOST_CRON_TASK, "team.profit.cron");
|
|
|
+ int star = callInt(host, "snapshotMonthlyStar", 55L, 2026, 11);
|
|
|
+ assertTrue(star >= 1 && star <= 6,
|
|
|
+ "月度重评必须落 team_star_snapshot 快照并返回本期星级 1-6(§4.3/§4.4),star=" + star);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §4.3 星级档位门槛(推荐校准值) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void starTiers_thresholds_sixTiersMatchSpec() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(0, callInt(host, "thresholdOfStar", 1), "一星门槛 = 0(入团即一星)");
|
|
|
+ assertEquals(8000, callInt(host, "thresholdOfStar", 2), "二星门槛 = 8,000");
|
|
|
+ assertEquals(25000, callInt(host, "thresholdOfStar", 3), "三星门槛 = 25,000");
|
|
|
+ assertEquals(70000, callInt(host, "thresholdOfStar", 4), "四星门槛 = 70,000");
|
|
|
+ assertEquals(140000, callInt(host, "thresholdOfStar", 5), "五星门槛 = 140,000");
|
|
|
+ assertEquals(250000, callInt(host, "thresholdOfStar", 6), "六星门槛 = 250,000");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void starForScore_bandMapping_specPortrait() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(1, callInt(host, "starForScore", 0L), "score=0 入团即一星(§4.3)");
|
|
|
+ assertEquals(1, callInt(host, "starForScore", 6500L), "兼职新规划师 6,500 分仍未达二星(跳一跳够得着)");
|
|
|
+ assertEquals(2, callInt(host, "starForScore", 8000L), "score=8,000 达二星");
|
|
|
+ assertEquals(3, callInt(host, "starForScore", 26000L), "活跃规划师 ≈26,000 分达三星");
|
|
|
+ assertEquals(4, callInt(host, "starForScore", 71000L), "资深规划师 ≈71,000 分达四星");
|
|
|
+ assertEquals(5, callInt(host, "starForScore", 145000L), "带教负责人 ≈145,000 分达五星");
|
|
|
+ assertEquals(6, callInt(host, "starForScore", 250000L), "顶流团队长 250,000 分达六星");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void starForScore_boundary_exactThresholdNotBelow() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(2, callInt(host, "starForScore", 24999L), "24,999 分仍二星(未达三星 25,000)");
|
|
|
+ assertEquals(3, callInt(host, "starForScore", 25000L), "25,000 分恰达三星");
|
|
|
+ assertEquals(5, callInt(host, "starForScore", 139999L), "139,999 分仍四星(未达五星 140,000)");
|
|
|
+ assertEquals(6, callInt(host, "starForScore", 140000L), "140,000 分达五星(六星需 250,000)");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §4.3 升级双门槛(#15 拍板) ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void isPromotable_scoreAndRefs_bothRequired() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertTrue(callBoolean(host, "isPromotable", 72000L, 1, 4),
|
|
|
+ "业绩分 ≥ 4★门槛 且 团队直推 ≥1 → 允许升级四星");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void isPromotable_scoreEnoughButNoRefs_notPromotable() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertFalse(callBoolean(host, "isPromotable", 72000L, 0, 4),
|
|
|
+ "业绩分够但滚动 12 个月团队直推 =0 → 禁止升级(升级双门槛缺一不可,§4.3)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void isPromotable_refsEnoughButScoreBelow_notPromotable() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertFalse(callBoolean(host, "isPromotable", 69999L, 2, 4),
|
|
|
+ "团队直推 ≥1 但业绩分未达 4★ 门槛 70,000 → 禁止升级");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void isPromotable_targetStarFarBeyondScore_notPromotable() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertFalse(callBoolean(host, "isPromotable", 72000L, 1, 5),
|
|
|
+ "业绩分 72,000 未达 5★ 门槛 140,000 → 即使有直推也不能直升五星(逐级门槛)");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== §4.3 重评:升星过双门槛、降星自动 ====================
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reEvaluateStar_upgrade_withDualGatePassed() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(4, callInt(host, "reEvaluateStar", 72000L, 1, 3),
|
|
|
+ "业绩分 72,000(≥4★ 门槛)+ 直推 1 → 从 3★ 升级到 4★");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reEvaluateStar_scoreEnoughRefsMissing_stayCurrentNotUpgrade() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(3, callInt(host, "reEvaluateStar", 72000L, 0, 3),
|
|
|
+ "业绩分够 4★ 但本窗口无团队直推 → 无法升级,维持当前 3★(§4.3 升级双门槛)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reEvaluateStar_scoreFalls_autoDowngradeNoGate() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(2, callInt(host, "reEvaluateStar", 24999L, 0, 4),
|
|
|
+ "滚动窗口滑出业绩分降至 2★ 档 → 自动降级(降星不受直推门槛限制,不保留历史级别)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void reEvaluateStar_joinStarScoreZero_keepOneStar() {
|
|
|
+ Object host = contractHost(HOST_STAR_CALC, "team.profit.cron");
|
|
|
+ assertEquals(1, callInt(host, "reEvaluateStar", 0L, 0, 1),
|
|
|
+ "入团一星(score=0)重评仍为一星");
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 反射辅助(锚定宿主契约,避免编译期依赖不存在类) ====================
|
|
|
+
|
|
|
+ private Object contractHost(String className, String capabilityKey) {
|
|
|
+ try {
|
|
|
+ Class<?> clazz = Class.forName(className);
|
|
|
+ return clazz.getDeclaredConstructor().newInstance();
|
|
|
+ } catch (ClassNotFoundException e) {
|
|
|
+ fail("RED[" + capabilityKey + "] 契约宿主 " + className + " 未实现(实现未落地):"
|
|
|
+ + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("契约宿主 " + className + " 实例化失败:" + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 按方法名 + 参数个数匹配并调用(宽松适配基本/包装类型),契约方法缺失即断言失败。 */
|
|
|
+ private Object call(Object host, String method, Object... args) {
|
|
|
+ for (Method m : host.getClass().getMethods()) {
|
|
|
+ if (m.getName().equals(method) && m.getParameterCount() == args.length) {
|
|
|
+ try {
|
|
|
+ return m.invoke(host, args);
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("契约方法 " + host.getClass().getName() + "#" + method + " 调用失败:" + e.getMessage());
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ fail("RED[team.profit.cron] 契约方法 " + host.getClass().getName() + "#" + method + " 未实现");
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+
|
|
|
+ private int callInt(Object host, String method, Object... args) {
|
|
|
+ Object r = call(host, method, args);
|
|
|
+ if (r instanceof Number) {
|
|
|
+ return ((Number) r).intValue();
|
|
|
+ }
|
|
|
+ fail("契约方法 " + method + " 应返回整数,实际 " + r);
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+
|
|
|
+ private long callLong(Object host, String method, Object... args) {
|
|
|
+ Object r = call(host, method, args);
|
|
|
+ if (r instanceof Number) {
|
|
|
+ return ((Number) r).longValue();
|
|
|
+ }
|
|
|
+ fail("契约方法 " + method + " 应返回长整数,实际 " + r);
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean callBoolean(Object host, String method, Object... args) {
|
|
|
+ Object r = call(host, method, args);
|
|
|
+ if (r instanceof Boolean) {
|
|
|
+ return (Boolean) r;
|
|
|
+ }
|
|
|
+ fail("契约方法 " + method + " 应返回布尔,实际 " + r);
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+
|
|
|
+ private String callString(Object host, String method, Object... args) {
|
|
|
+ Object r = call(host, method, args);
|
|
|
+ if (r instanceof String) {
|
|
|
+ return (String) r;
|
|
|
+ }
|
|
|
+ fail("契约方法 " + method + " 应返回字符串,实际 " + r);
|
|
|
+ throw new IllegalStateException("unreachable");
|
|
|
+ }
|
|
|
+}
|