|
@@ -42,14 +42,14 @@ func c1Assertions(
|
|
|
// 同上:校区余额"恰好是 0"必须排除 NULL —— NULL 的值也是 0,直接比 int64 会空过
|
|
// 同上:校区余额"恰好是 0"必须排除 NULL —— NULL 的值也是 0,直接比 int64 会空过
|
|
|
okL := isZero(lg.After.Campus.Balance, lg.After.Campus.Nulls.Balance) &&
|
|
okL := isZero(lg.After.Campus.Balance, lg.After.Campus.Nulls.Balance) &&
|
|
|
isZero(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance)
|
|
isZero(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance)
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "清零:**校区**余额恰好归 0(「清零」清的是校区这一行)",
|
|
Name: "清零:**校区**余额恰好归 0(「清零」清的是校区这一行)",
|
|
|
Pass: okL,
|
|
Pass: okL,
|
|
|
Detail: fmt.Sprintf("legacy 校区余额后=%s | target 校区余额后=%s(都应为 0.00,且不能是 NULL)",
|
|
Detail: fmt.Sprintf("legacy 校区余额后=%s | target 校区余额后=%s(都应为 0.00,且不能是 NULL)",
|
|
|
moneyStr(lg.After.Campus.Balance, lg.After.Campus.Nulls.Balance),
|
|
moneyStr(lg.After.Campus.Balance, lg.After.Campus.Nulls.Balance),
|
|
|
moneyStr(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance)),
|
|
moneyStr(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance)),
|
|
|
})
|
|
})
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "清零:全局余额只减 campusBalance(**不是**归 0,除非原本只有一个校区有钱)",
|
|
Name: "清零:全局余额只减 campusBalance(**不是**归 0,除非原本只有一个校区有钱)",
|
|
|
Pass: dGlobal.Balance == -lg.Before.Campus.Balance && tGlobal.Balance == -tg.Before.Campus.Balance,
|
|
Pass: dGlobal.Balance == -lg.Before.Campus.Balance && tGlobal.Balance == -tg.Before.Campus.Balance,
|
|
|
Detail: fmt.Sprintf("legacy 全局 %s→%s(Δ%s,campusBalance=%s)| target 全局 %s→%s(Δ%s,campusBalance=%s)",
|
|
Detail: fmt.Sprintf("legacy 全局 %s→%s(Δ%s,campusBalance=%s)| target 全局 %s→%s(Δ%s,campusBalance=%s)",
|
|
@@ -62,7 +62,7 @@ func c1Assertions(
|
|
|
okT := dGlobal.Special == lg.Before.Campus.Balance && dCampus.Special == lg.Before.Campus.Balance &&
|
|
okT := dGlobal.Special == lg.Before.Campus.Balance && dCampus.Special == lg.Before.Campus.Balance &&
|
|
|
tGlobal.Special == tg.Before.Campus.Balance && tCampus.Special == tg.Before.Campus.Balance &&
|
|
tGlobal.Special == tg.Before.Campus.Balance && tCampus.Special == tg.Before.Campus.Balance &&
|
|
|
dGlobal.Spend == lg.Before.Campus.Balance && tGlobal.Spend == tg.Before.Campus.Balance
|
|
dGlobal.Spend == lg.Before.Campus.Balance && tGlobal.Spend == tg.Before.Campus.Balance
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "清零:special_expenses 与 spend **同额增加** campusBalance(三列一起动,只动一列会红)",
|
|
Name: "清零:special_expenses 与 spend **同额增加** campusBalance(三列一起动,只动一列会红)",
|
|
|
Pass: okT,
|
|
Pass: okT,
|
|
|
Detail: fmt.Sprintf("legacy 全局 Δspec=%s Δspend=%s | target 全局 Δspec=%s Δspend=%s(期望各 +%s / +%s)",
|
|
Detail: fmt.Sprintf("legacy 全局 Δspec=%s Δspend=%s | target 全局 Δspec=%s Δspend=%s(期望各 +%s / +%s)",
|
|
@@ -71,7 +71,7 @@ func c1Assertions(
|
|
|
yuan(lg.Before.Campus.Balance), yuan(tg.Before.Campus.Balance)),
|
|
yuan(lg.Before.Campus.Balance), yuan(tg.Before.Campus.Balance)),
|
|
|
})
|
|
})
|
|
|
// 无请求号 → log_id / request_id 都应为空("不享幂等"是 SP 签名决定的,不是漏写)
|
|
// 无请求号 → log_id / request_id 都应为空("不享幂等"是 SP 签名决定的,不是漏写)
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "清零:流水 log_id 与 request_id **都为空**(SP10 没有请求号入参,天然不幂等)",
|
|
Name: "清零:流水 log_id 与 request_id **都为空**(SP10 没有请求号入参,天然不幂等)",
|
|
|
Pass: allLogsWithoutID(lg.After.Logs) && allLogsWithoutID(tg.After.Logs),
|
|
Pass: allLogsWithoutID(lg.After.Logs) && allLogsWithoutID(tg.After.Logs),
|
|
|
Detail: fmt.Sprintf("legacy %d 条 %s | target %d 条 %s",
|
|
Detail: fmt.Sprintf("legacy %d 条 %s | target %d 条 %s",
|
|
@@ -82,14 +82,14 @@ func c1Assertions(
|
|
|
|
|
|
|
|
// ④ 取消充值活动:流水 type=209、money=−m,且报名状态归 **0**(不是 4)
|
|
// ④ 取消充值活动:流水 type=209、money=−m,且报名状态归 **0**(不是 4)
|
|
|
if c.Op == OpCancelRechargeActivity {
|
|
if c.Op == OpCancelRechargeActivity {
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "取消充值活动:流水 type 必须是 **209**、money 必须是 **−金额**(照 dump,不改符号)",
|
|
Name: "取消充值活动:流水 type 必须是 **209**、money 必须是 **−金额**(照 dump,不改符号)",
|
|
|
Pass: logsAllType(lg.After.Logs, 209) && logsAllType(tg.After.Logs, 209) &&
|
|
Pass: logsAllType(lg.After.Logs, 209) && logsAllType(tg.After.Logs, 209) &&
|
|
|
logsAllMoney(lg.After.Logs, -c.AmountCents) && logsAllMoney(tg.After.Logs, -c.AmountCents),
|
|
logsAllMoney(lg.After.Logs, -c.AmountCents) && logsAllMoney(tg.After.Logs, -c.AmountCents),
|
|
|
Detail: fmt.Sprintf("legacy %s | target %s(期望 type=209 money=%s ×%d)",
|
|
Detail: fmt.Sprintf("legacy %s | target %s(期望 type=209 money=%s ×%d)",
|
|
|
fmtLogs(lg.After.Logs), fmtLogs(tg.After.Logs), yuan(-c.AmountCents), runsLegacy),
|
|
fmtLogs(lg.After.Logs), fmtLogs(tg.After.Logs), yuan(-c.AmountCents), runsLegacy),
|
|
|
})
|
|
})
|
|
|
- out = append(out, Assertion{
|
|
|
|
|
|
|
+ out = append(out, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "取消充值活动:报名状态归 **0(取消报名)**,不是 4(过期)—— SP8 与 SP10 的差别",
|
|
Name: "取消充值活动:报名状态归 **0(取消报名)**,不是 4(过期)—— SP8 与 SP10 的差别",
|
|
|
Pass: signedAll(c, lg, 0) && signedAll(c, tg, 0),
|
|
Pass: signedAll(c, lg, 0) && signedAll(c, tg, 0),
|
|
|
Detail: fmt.Sprintf("legacy %s | target %s(活动 %d 应为 0)",
|
|
Detail: fmt.Sprintf("legacy %s | target %s(活动 %d 应为 0)",
|
|
@@ -106,7 +106,7 @@ func c1Assertions(
|
|
|
func campusMetaAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
func campusMetaAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
lm, tm := lg.CampusMeta, tg.CampusMeta
|
|
lm, tm := lg.CampusMeta, tg.CampusMeta
|
|
|
if lm == nil || tm == nil {
|
|
if lm == nil || tm == nil {
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "两侧看到的校区元数据一致(机构 + 是否自营)",
|
|
Name: "两侧看到的校区元数据一致(机构 + 是否自营)",
|
|
|
Pass: false,
|
|
Pass: false,
|
|
|
Detail: "有一侧没读到校区元数据(读失败或未触发读取)",
|
|
Detail: "有一侧没读到校区元数据(读失败或未触发读取)",
|
|
@@ -115,7 +115,7 @@ func campusMetaAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
// CampusMissing:两侧都应该**查不到**这个校区(不是"查到了但值是 0")
|
|
// CampusMissing:两侧都应该**查不到**这个校区(不是"查到了但值是 0")
|
|
|
if c.CampusMissing {
|
|
if c.CampusMissing {
|
|
|
ok := lm.Err != "" && tm.Err != "" && lm.InstitutionID == 0 && tm.InstitutionID == 0
|
|
ok := lm.Err != "" && tm.Err != "" && lm.InstitutionID == 0 && tm.InstitutionID == 0
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "两侧都**查不到**该校区(故意用不存在的校区 id)",
|
|
Name: "两侧都**查不到**该校区(故意用不存在的校区 id)",
|
|
|
Pass: ok,
|
|
Pass: ok,
|
|
|
Detail: fmt.Sprintf("legacy 视图:机构=%d%s | target t_campus:机构=%d%s(都应为 0 + 查不到)",
|
|
Detail: fmt.Sprintf("legacy 视图:机构=%d%s | target t_campus:机构=%d%s(都应为 0 + 查不到)",
|
|
@@ -132,7 +132,7 @@ func campusMetaAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
lm.InstitutionID, lm.SelfSupport, errSuffix(lm.Err),
|
|
lm.InstitutionID, lm.SelfSupport, errSuffix(lm.Err),
|
|
|
tm.InstitutionID, tm.SelfSupport, errSuffix(tm.Err),
|
|
tm.InstitutionID, tm.SelfSupport, errSuffix(tm.Err),
|
|
|
c.InstitutionID, wantSelf)
|
|
c.InstitutionID, wantSelf)
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "两侧看到的校区元数据一致(机构 + 是否自营)—— 不同就会走不同失效分支",
|
|
Name: "两侧看到的校区元数据一致(机构 + 是否自营)—— 不同就会走不同失效分支",
|
|
|
Pass: ok,
|
|
Pass: ok,
|
|
|
Detail: detail,
|
|
Detail: detail,
|
|
@@ -158,7 +158,7 @@ func campusAccountAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
reason = "(NoCampusAccount:校区存在但该用户没有账户行)"
|
|
reason = "(NoCampusAccount:校区存在但该用户没有账户行)"
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "调用前:校区账户行" + word + reason,
|
|
Name: "调用前:校区账户行" + word + reason,
|
|
|
Pass: ok,
|
|
Pass: ok,
|
|
|
Detail: fmt.Sprintf("legacy 存在=%v | target 存在=%v(期望都为 %v)",
|
|
Detail: fmt.Sprintf("legacy 存在=%v | target 存在=%v(期望都为 %v)",
|
|
@@ -174,7 +174,7 @@ func campusAccountAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
// 单独把"行被建出来了"钉成一条,红的时候一眼知道是建户分支没走到。
|
|
// 单独把"行被建出来了"钉成一条,红的时候一眼知道是建户分支没走到。
|
|
|
func campusAccountCreatedAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
func campusAccountCreatedAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
ok := lg.After.HasCampusAccount && tg.After.HasCampusAccount
|
|
ok := lg.After.HasCampusAccount && tg.After.HasCampusAccount
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "调用后:校区账户行被建出来(dump 的 `IF logCount=0 THEN INSERT` 分支)",
|
|
Name: "调用后:校区账户行被建出来(dump 的 `IF logCount=0 THEN INSERT` 分支)",
|
|
|
Pass: ok,
|
|
Pass: ok,
|
|
|
Detail: fmt.Sprintf("legacy 存在=%v 余额=%s | target 存在=%v 余额=%s(都应为 true)",
|
|
Detail: fmt.Sprintf("legacy 存在=%v 余额=%s | target 存在=%v 余额=%s(都应为 true)",
|
|
@@ -204,7 +204,7 @@ func signedAssertion(c Case, lg, tg *SideResult) Assertion {
|
|
|
fmtSigned(want),
|
|
fmtSigned(want),
|
|
|
fmtSigned(lg.After.Signed), fmtChanges(lg.Before.Signed, lg.After.Signed),
|
|
fmtSigned(lg.After.Signed), fmtChanges(lg.Before.Signed, lg.After.Signed),
|
|
|
fmtSigned(tg.After.Signed), fmtChanges(tg.Before.Signed, tg.After.Signed))
|
|
fmtSigned(tg.After.Signed), fmtChanges(tg.Before.Signed, tg.After.Signed))
|
|
|
- return Assertion{Name: name, Pass: ok, Detail: detail}
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness, Name: name, Pass: ok, Detail: detail}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// opExpiresActivities 标记"该 Op 会把可失效报名置 4"(而不是置 0)。
|
|
// opExpiresActivities 标记"该 Op 会把可失效报名置 4"(而不是置 0)。
|
|
@@ -222,14 +222,14 @@ func opExpiresActivities(op Op) bool {
|
|
|
// spReturnAssertion 断言 SP 自己 SELECT 出来的结果集(这是 SP 契约的一部分)。
|
|
// spReturnAssertion 断言 SP 自己 SELECT 出来的结果集(这是 SP 契约的一部分)。
|
|
|
func spReturnAssertion(c Case, lg *SideResult) Assertion {
|
|
func spReturnAssertion(c Case, lg *SideResult) Assertion {
|
|
|
if len(lg.Calls) == 0 || lg.Calls[0].Returned == nil {
|
|
if len(lg.Calls) == 0 || lg.Calls[0].Returned == nil {
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "SP8 返回值:success=1 / msg=ok / schoolid 由校区反查得到",
|
|
Name: "SP8 返回值:success=1 / msg=ok / schoolid 由校区反查得到",
|
|
|
Pass: false, Detail: "没有读到 SP 的结果集(SP8 每条路径都应以 SELECT 结尾)",
|
|
Pass: false, Detail: "没有读到 SP 的结果集(SP8 每条路径都应以 SELECT 结尾)",
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
r := lg.Calls[0].Returned
|
|
r := lg.Calls[0].Returned
|
|
|
ok := r["success"] == "1" && r["msg"] == "ok" && r["schoolid"] == fmt.Sprint(c.InstitutionID)
|
|
ok := r["success"] == "1" && r["msg"] == "ok" && r["schoolid"] == fmt.Sprint(c.InstitutionID)
|
|
|
- return Assertion{
|
|
|
|
|
|
|
+ return Assertion{Kind: AssertionBusiness,
|
|
|
Name: "SP8 返回值:success=1 / msg=ok / schoolid 由校区反查得到",
|
|
Name: "SP8 返回值:success=1 / msg=ok / schoolid 由校区反查得到",
|
|
|
Pass: ok,
|
|
Pass: ok,
|
|
|
Detail: fmt.Sprintf("实测 schoolid=%q success=%q msg=%q(期望 %q / \"1\" / \"ok\")",
|
|
Detail: fmt.Sprintf("实测 schoolid=%q success=%q msg=%q(期望 %q / \"1\" / \"ok\")",
|
|
@@ -281,23 +281,23 @@ func requestIDOverflowCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
asserts = append(asserts,
|
|
asserts = append(asserts,
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "前提:入参请求号 **确实 33 字符**(> legacy 的 32,否则本条退化成普通 case)",
|
|
Name: "前提:入参请求号 **确实 33 字符**(> legacy 的 32,否则本条退化成普通 case)",
|
|
|
Pass: len(c.RequestID) == 33,
|
|
Pass: len(c.RequestID) == 33,
|
|
|
Detail: fmt.Sprintf("len(requestId)=%d(期望 33)", len(c.RequestID)),
|
|
Detail: fmt.Sprintf("len(requestId)=%d(期望 33)", len(c.RequestID)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy 报的是 **1406(Data too long)且点名字段 logid** —— 证明是列宽不是逻辑",
|
|
Name: "legacy 报的是 **1406(Data too long)且点名字段 logid** —— 证明是列宽不是逻辑",
|
|
|
Pass: !lgOK && strings.Contains(lgErr, "1406") && strings.Contains(strings.ToLower(lgErr), "logid"),
|
|
Pass: !lgOK && strings.Contains(lgErr, "1406") && strings.Contains(strings.ToLower(lgErr), "logid"),
|
|
|
Detail: fmt.Sprintf("legacy success=%v err=%q", lgOK, lgErr),
|
|
Detail: fmt.Sprintf("legacy success=%v err=%q", lgOK, lgErr),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy **钱已扣、流水 0 条**(同一次 CALL 里前置 UPDATE 不回滚)—— 缺陷在 legacy 一侧",
|
|
Name: "legacy **钱已扣、流水 0 条**(同一次 CALL 里前置 UPDATE 不回滚)—— 缺陷在 legacy 一侧",
|
|
|
Pass: dGlobal.Balance == want && dCampus.Balance == want && len(lg.After.Logs) == 0,
|
|
Pass: dGlobal.Balance == want && dCampus.Balance == want && len(lg.After.Logs) == 0,
|
|
|
Detail: fmt.Sprintf("Δ全局=%s Δ校区=%s 流水=%d 条(期望各 %s、0 条)",
|
|
Detail: fmt.Sprintf("Δ全局=%s Δ校区=%s 流水=%d 条(期望各 %s、0 条)",
|
|
|
yuan(dGlobal.Balance), yuan(dCampus.Balance), len(lg.After.Logs), yuan(want)),
|
|
yuan(dGlobal.Balance), yuan(dCampus.Balance), len(lg.After.Logs), yuan(want)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "target 成功且 **33 字符请求号完整落库**(没被静默截断 / 没报错)",
|
|
Name: "target 成功且 **33 字符请求号完整落库**(没被静默截断 / 没报错)",
|
|
|
Pass: tgOK && len(tg.After.Logs) == 1 &&
|
|
Pass: tgOK && len(tg.After.Logs) == 1 &&
|
|
|
tg.After.Logs[0].RequestID == c.RequestID && tg.After.Logs[0].LogID == c.RequestID &&
|
|
tg.After.Logs[0].RequestID == c.RequestID && tg.After.Logs[0].LogID == c.RequestID &&
|
|
@@ -306,7 +306,7 @@ func requestIDOverflowCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
tgOK, len(tg.After.Logs), len(reqIDOf(tg.After.Logs)), len(logIDOf(tg.After.Logs)),
|
|
tgOK, len(tg.After.Logs), len(reqIDOf(tg.After.Logs)), len(logIDOf(tg.After.Logs)),
|
|
|
true, len(c.RequestID), len(c.RequestID)),
|
|
true, len(c.RequestID), len(c.RequestID)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "两侧 Δbalance 相同(钱的结果一致,差异只在**有没有凭证**)",
|
|
Name: "两侧 Δbalance 相同(钱的结果一致,差异只在**有没有凭证**)",
|
|
|
Pass: dGlobal.Balance == tGlobal.Balance && dCampus.Balance == tCampus.Balance,
|
|
Pass: dGlobal.Balance == tGlobal.Balance && dCampus.Balance == tCampus.Balance,
|
|
|
Detail: fmt.Sprintf("全局 legacy=%s target=%s | 校区 legacy=%s target=%s",
|
|
Detail: fmt.Sprintf("全局 legacy=%s target=%s | 校区 legacy=%s target=%s",
|
|
@@ -388,37 +388,37 @@ func campusRowNullCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion) {
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
asserts = append(asserts,
|
|
asserts = append(asserts,
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "前提:两侧调用前**都**没有该校区账户行(否则本条测不到建行分支)",
|
|
Name: "前提:两侧调用前**都**没有该校区账户行(否则本条测不到建行分支)",
|
|
|
Pass: !lg.Before.HasCampusAccount && !tg.Before.HasCampusAccount,
|
|
Pass: !lg.Before.HasCampusAccount && !tg.Before.HasCampusAccount,
|
|
|
Detail: fmt.Sprintf("legacy 存在=%v | target 存在=%v(都应为 false)",
|
|
Detail: fmt.Sprintf("legacy 存在=%v | target 存在=%v(都应为 false)",
|
|
|
lg.Before.HasCampusAccount, tg.Before.HasCampusAccount),
|
|
lg.Before.HasCampusAccount, tg.Before.HasCampusAccount),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:校区行**被建出来了**,且五列金额**全是 NULL**(不是 0,也不是 −金额)",
|
|
Name: "legacy:校区行**被建出来了**,且五列金额**全是 NULL**(不是 0,也不是 −金额)",
|
|
|
Pass: lg.After.HasCampusAccount && lgAllNull,
|
|
Pass: lg.After.HasCampusAccount && lgAllNull,
|
|
|
Detail: fmt.Sprintf("legacy 行存在=%v NULL 列=%s(期望 true / 五列全 NULL)",
|
|
Detail: fmt.Sprintf("legacy 行存在=%v NULL 列=%s(期望 true / 五列全 NULL)",
|
|
|
lg.After.HasCampusAccount, lg.After.Campus.Nulls.String()),
|
|
lg.After.HasCampusAccount, lg.After.Campus.Nulls.String()),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:**Δ校区也要如实表示 NULL**——不能因为读成 0 就显示成 「Δ=0.00」(那与「扣了 0 元」无法区分)",
|
|
Name: "legacy:**Δ校区也要如实表示 NULL**——不能因为读成 0 就显示成 「Δ=0.00」(那与「扣了 0 元」无法区分)",
|
|
|
Pass: dCampus.Nulls.Any(),
|
|
Pass: dCampus.Nulls.Any(),
|
|
|
Detail: fmt.Sprintf("Δ校区 balance=%s(NULL 列=%s)",
|
|
Detail: fmt.Sprintf("Δ校区 balance=%s(NULL 列=%s)",
|
|
|
moneyStr(dCampus.Balance, dCampus.Nulls.Balance), dCampus.Nulls.String()),
|
|
moneyStr(dCampus.Balance, dCampus.Nulls.Balance), dCampus.Nulls.String()),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:**全局**被扣了 −金额(`t_user_expense` 的列有 DEFAULT 0.000)—— 同一笔业务两表矛盾",
|
|
Name: "legacy:**全局**被扣了 −金额(`t_user_expense` 的列有 DEFAULT 0.000)—— 同一笔业务两表矛盾",
|
|
|
Pass: dGlobal.Balance == want && dGlobal.Income == want,
|
|
Pass: dGlobal.Balance == want && dGlobal.Income == want,
|
|
|
Detail: fmt.Sprintf("Δ全局 balance=%s total_income=%s(期望各 %s)",
|
|
Detail: fmt.Sprintf("Δ全局 balance=%s total_income=%s(期望各 %s)",
|
|
|
yuan(dGlobal.Balance), yuan(dGlobal.Income), yuan(want)),
|
|
yuan(dGlobal.Balance), yuan(dGlobal.Income), yuan(want)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:流水照写 1 条 type=209 / money=−金额(**钱没扣,凭证有了**)",
|
|
Name: "legacy:流水照写 1 条 type=209 / money=−金额(**钱没扣,凭证有了**)",
|
|
|
Pass: len(lg.After.Logs) == 1 && lg.After.Logs[0].Type == 209 &&
|
|
Pass: len(lg.After.Logs) == 1 && lg.After.Logs[0].Type == 209 &&
|
|
|
lg.After.Logs[0].Money == want,
|
|
lg.After.Logs[0].Money == want,
|
|
|
Detail: fmt.Sprintf("legacy %s(期望 type=209 money=%s ×1)", fmtLogs(lg.After.Logs), yuan(want)),
|
|
Detail: fmt.Sprintf("legacy %s(期望 type=209 money=%s ×1)", fmtLogs(lg.After.Logs), yuan(want)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "target:校区行被建出来、**五列都不为 NULL**、且真扣成 −金额(列是 NOT NULL DEFAULT 0.00)",
|
|
Name: "target:校区行被建出来、**五列都不为 NULL**、且真扣成 −金额(列是 NOT NULL DEFAULT 0.00)",
|
|
|
Pass: tg.After.HasCampusAccount && tgNoNull &&
|
|
Pass: tg.After.HasCampusAccount && tgNoNull &&
|
|
|
tg.After.Campus.Balance == want && tCampus.Balance == want && tCampus.Income == want,
|
|
tg.After.Campus.Balance == want && tCampus.Balance == want && tCampus.Income == want,
|
|
@@ -429,7 +429,7 @@ func campusRowNullCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion) {
|
|
|
moneyStr(tCampus.Income, tCampus.Nulls.Income),
|
|
moneyStr(tCampus.Income, tCampus.Nulls.Income),
|
|
|
yuan(want), yuan(want), yuan(want)),
|
|
yuan(want), yuan(want), yuan(want)),
|
|
|
},
|
|
},
|
|
|
- Assertion{
|
|
|
|
|
|
|
+ Assertion{Kind: AssertionBusiness,
|
|
|
Name: "判定:**LEGACY-DEFECT** —— legacy 两表自相矛盾(校区五列 NULL / 全局 −金额),target 侧正确,不跟",
|
|
Name: "判定:**LEGACY-DEFECT** —— legacy 两表自相矛盾(校区五列 NULL / 全局 −金额),target 侧正确,不跟",
|
|
|
Pass: lgAllNull && tgNoNull &&
|
|
Pass: lgAllNull && tgNoNull &&
|
|
|
dGlobal == tGlobal &&
|
|
dGlobal == tGlobal &&
|
|
@@ -484,7 +484,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
|
|
|
|
|
// ① 前提
|
|
// ① 前提
|
|
|
baseOK := campusBefore == tg.Before.Campus.Balance && totalBefore == tg.Before.Global.Balance
|
|
baseOK := campusBefore == tg.Before.Campus.Balance && totalBefore == tg.Before.Global.Balance
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "前提:两侧 **2 位**基线逐分相等(校区与全局都要相等)",
|
|
Name: "前提:两侧 **2 位**基线逐分相等(校区与全局都要相等)",
|
|
|
Pass: baseOK,
|
|
Pass: baseOK,
|
|
|
Detail: fmt.Sprintf("legacy 校区=%s 全局=%s | target 校区=%s 全局=%s",
|
|
Detail: fmt.Sprintf("legacy 校区=%s 全局=%s | target 校区=%s 全局=%s",
|
|
@@ -493,7 +493,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// ② legacy 的 3 位判据必须**成立**(否则构型选错了,这条 case 什么都证明不了)
|
|
// ② legacy 的 3 位判据必须**成立**(否则构型选错了,这条 case 什么都证明不了)
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:3 位判据 `校区 > 全局` **成立**(构型前提;不成立说明选错人)",
|
|
Name: "legacy:3 位判据 `校区 > 全局` **成立**(构型前提;不成立说明选错人)",
|
|
|
Pass: c3ok && t3ok && c3 > t3,
|
|
Pass: c3ok && t3ok && c3 > t3,
|
|
|
Detail: fmt.Sprintf("campus=%s total=%s → %s > %s 成立=%v",
|
|
Detail: fmt.Sprintf("campus=%s total=%s → %s > %s 成立=%v",
|
|
@@ -501,7 +501,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// ③ target 的 2 位判据必须**不成立**(这就是"放行"的原因)
|
|
// ③ target 的 2 位判据必须**不成立**(这就是"放行"的原因)
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "target:2 位判据 `校区 > 全局` **不成立** ⇒ 放行(分歧的成因:第 3 位被收敛抹平)",
|
|
Name: "target:2 位判据 `校区 > 全局` **不成立** ⇒ 放行(分歧的成因:第 3 位被收敛抹平)",
|
|
|
Pass: tg.Before.Campus.Balance <= tg.Before.Global.Balance,
|
|
Pass: tg.Before.Campus.Balance <= tg.Before.Global.Balance,
|
|
|
Detail: fmt.Sprintf("campus=%s total=%s → %s > %s 不成立",
|
|
Detail: fmt.Sprintf("campus=%s total=%s → %s > %s 不成立",
|
|
@@ -512,7 +512,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
// ④ legacy 行为:LEAVE + 零写入 + 0 流水
|
|
// ④ legacy 行为:LEAVE + 零写入 + 0 流水
|
|
|
lgLEAVE := len(lg.Calls) > 0 && lg.Calls[0].Success
|
|
lgLEAVE := len(lg.Calls) > 0 && lg.Calls[0].Success
|
|
|
lgNoWrite := lg.Before.Global == lg.After.Global && lg.Before.Campus == lg.After.Campus
|
|
lgNoWrite := lg.Before.Global == lg.After.Global && lg.Before.Campus == lg.After.Campus
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "legacy:`LEAVE` 静默返回、**零写入**、0 条流水",
|
|
Name: "legacy:`LEAVE` 静默返回、**零写入**、0 条流水",
|
|
|
Pass: lgLEAVE && lgNoWrite && len(lg.After.Logs) == 0,
|
|
Pass: lgLEAVE && lgNoWrite && len(lg.After.Logs) == 0,
|
|
|
Detail: fmt.Sprintf("抛错=%v 全局变=%v 校区变=%v 流水=%d(都应为 false/false/0)",
|
|
Detail: fmt.Sprintf("抛错=%v 全局变=%v 校区变=%v 流水=%d(都应为 false/false/0)",
|
|
@@ -526,7 +526,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
logOK := len(tg.After.Logs) == 1 && tg.After.Logs[0].Type == 208 &&
|
|
logOK := len(tg.After.Logs) == 1 && tg.After.Logs[0].Type == 208 &&
|
|
|
tg.After.Logs[0].Money == -amount && tg.After.Logs[0].LogID == "" && tg.After.Logs[0].RequestID == ""
|
|
tg.After.Logs[0].Money == -amount && tg.After.Logs[0].LogID == "" && tg.After.Logs[0].RequestID == ""
|
|
|
tgOK := len(tg.Calls) > 0 && tg.Calls[0].Success
|
|
tgOK := len(tg.Calls) > 0 && tg.Calls[0].Success
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "target:清零成功 —— 校区 0、全局 0、special 与 spend 各 +金额、1 条 `type=208 money=−金额`(无 logid/request_id)",
|
|
Name: "target:清零成功 —— 校区 0、全局 0、special 与 spend 各 +金额、1 条 `type=208 money=−金额`(无 logid/request_id)",
|
|
|
Pass: tgOK && isZero(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance) &&
|
|
Pass: tgOK && isZero(tg.After.Campus.Balance, tg.After.Campus.Nulls.Balance) &&
|
|
|
isZero(tg.After.Global.Balance, tg.After.Global.Nulls.Balance) &&
|
|
isZero(tg.After.Global.Balance, tg.After.Global.Nulls.Balance) &&
|
|
@@ -541,7 +541,7 @@ func predicateCollapseCompare(c Case, lg, tg *SideResult) ([]Diff, []Assertion)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// ⑥ 结论行:把归类写进报告,免得下一个人当它是缺陷
|
|
// ⑥ 结论行:把归类写进报告,免得下一个人当它是缺陷
|
|
|
- asserts = append(asserts, Assertion{
|
|
|
|
|
|
|
+ asserts = append(asserts, Assertion{Kind: AssertionBusiness,
|
|
|
Name: "判定:**PRECISION-CONVERGENCE / PREDICATE-COLLAPSE** —— 迁移的 2 位收敛抹平了判据输入,两侧分支不同,**不是缺陷**",
|
|
Name: "判定:**PRECISION-CONVERGENCE / PREDICATE-COLLAPSE** —— 迁移的 2 位收敛抹平了判据输入,两侧分支不同,**不是缺陷**",
|
|
|
Pass: true,
|
|
Pass: true,
|
|
|
Detail: fmt.Sprintf("legacy 1565.688>1565.687 成立 ⇒ LEAVE(未写入)|target %s>%s 不成立 ⇒ 清零 %s。"+
|
|
Detail: fmt.Sprintf("legacy 1565.688>1565.687 成立 ⇒ LEAVE(未写入)|target %s>%s 不成立 ⇒ 清零 %s。"+
|