|
@@ -139,10 +139,15 @@ func TestListUsableActivityIsolation(t *testing.T) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// TestSumUsableThreeParamScoping dump usable 3 参**等值**口径(audit A7,dump 763182-763183)。
|
|
|
|
|
|
|
+// TestSumUsableThreeParamScoping dump usable 3 参**等值且 campus 嵌套在 school 分支内**口径
|
|
|
|
|
+// (audit A7 二次修正,dump 763181-763185 逐字):
|
|
|
//
|
|
//
|
|
|
-// dump:`WHERE schoolid=? AND campusid=? AND userid=?`,两个作用域参数传 0 视为**不筛**(平台/机构级汇总)。
|
|
|
|
|
-// 关键差异(相对旧实现的 `IN (campus,0)` 超集):机构级池(campus=0)**不并入**指定校区可用额。
|
|
|
|
|
|
|
+// IF inputSchoolid>0 THEN selectCondition = " AND `schoolid`=… AND `campusid`=…;
|
|
|
|
|
+// ELSE selectCondition = ''; END IF;
|
|
|
|
|
+//
|
|
|
|
|
+// 两条推论(本用例的门禁点):
|
|
|
|
|
+// 1. `campusid=0` **不是"不限校区"**,而是等值匹配 `campus_id=0` 的"机构级池";
|
|
|
|
|
+// 2. `schoolid<=0` 时**一条作用域条件都不加**,传入的 `campusid` 被**忽略**(dump 无"只按校区筛"路径)。
|
|
|
//
|
|
//
|
|
|
// 池清单(用户 user,可用 = discount - used):
|
|
// 池清单(用户 user,可用 = discount - used):
|
|
|
//
|
|
//
|
|
@@ -162,30 +167,36 @@ func TestSumUsableThreeParamScoping(t *testing.T) {
|
|
|
repo := NewActivityDiscountRepo(db, &appscope.Scope{InstitutionID: 1, CampusID: 2})
|
|
repo := NewActivityDiscountRepo(db, &appscope.Scope{InstitutionID: 1, CampusID: 2})
|
|
|
ctx := context.Background()
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
- // ① 本校区分池:inst=1 AND campus=2 → pool1(10) + pool5(0) = 10.00(**不含**机构级池 15)
|
|
|
|
|
|
|
+ // ① (inst=1, campus=2) → 两个等值条件 → pool1(10) + pool5(0) = 10.00(**不含**机构级池 15)
|
|
|
if got, err := repo.SumUsable(ctx, user, 1, 2); err != nil || got.Cents() != 1000 {
|
|
if got, err := repo.SumUsable(ctx, user, 1, 2); err != nil || got.Cents() != 1000 {
|
|
|
t.Fatalf("SumUsable(1,2) 应为 10.00(等值口径,不含 campus=0 池),实得 %s err=%v", got.String(), err)
|
|
t.Fatalf("SumUsable(1,2) 应为 10.00(等值口径,不含 campus=0 池),实得 %s err=%v", got.String(), err)
|
|
|
}
|
|
}
|
|
|
- // ② 不限定校区:inst=1 → 10 + 15 + 100 = 125.00
|
|
|
|
|
- if got, err := repo.SumUsable(ctx, user, 1, 0); err != nil || got.Cents() != 12500 {
|
|
|
|
|
- t.Fatalf("SumUsable(1,0) 应为 125.00,实得 %s err=%v", got.String(), err)
|
|
|
|
|
|
|
+ // ② (inst=1, campus=0) → `campus_id = 0` **等值** → 只有机构级池 pool2 = 15.00
|
|
|
|
|
+ // (旧期望 125.00 是照"campusID=0 即不筛"的错误实现写的,dump 无此语义)
|
|
|
|
|
+ if got, err := repo.SumUsable(ctx, user, 1, 0); err != nil || got.Cents() != 1500 {
|
|
|
|
|
+ t.Fatalf("SumUsable(1,0) 应为 15.00(只命中 campus_id=0 的机构级池),实得 %s err=%v", got.String(), err)
|
|
|
}
|
|
}
|
|
|
- // ③ 平台视角:inst=0 且 campus=0 → 不过滤机构/校区 → 10+15+100+50 = 175.00
|
|
|
|
|
|
|
+ // ③ (0,0) 平台视角:selectCondition='' → 不过滤机构/校区 → 10+15+100+50 = 175.00
|
|
|
if got, err := repo.SumUsable(ctx, user, 0, 0); err != nil || got.Cents() != 17500 {
|
|
if got, err := repo.SumUsable(ctx, user, 0, 0); err != nil || got.Cents() != 17500 {
|
|
|
t.Fatalf("SumUsable(0,0) 平台视角应为 175.00,实得 %s err=%v", got.String(), err)
|
|
t.Fatalf("SumUsable(0,0) 平台视角应为 175.00,实得 %s err=%v", got.String(), err)
|
|
|
}
|
|
}
|
|
|
- // ④ 只按校区分池(跨机构同名校区):campus=2 → pool1(10) + pool4(50) = 60.00
|
|
|
|
|
- if got, err := repo.SumUsable(ctx, user, 0, 2); err != nil || got.Cents() != 6000 {
|
|
|
|
|
- t.Fatalf("SumUsable(0,2) 应为 60.00,实得 %s err=%v", got.String(), err)
|
|
|
|
|
|
|
+ // ④ (0,2) 平台视角下 **campusID 被忽略**(campusid 只出现在 school 分支内)→ 同 ③ = 175.00
|
|
|
|
|
+ // (旧期望 60.00 是"单独按校区过滤"的错误实现;dump 里没有这条路径)
|
|
|
|
|
+ if got, err := repo.SumUsable(ctx, user, 0, 2); err != nil || got.Cents() != 17500 {
|
|
|
|
|
+ t.Fatalf("SumUsable(0,2) 应忽略 campusID → 175.00,实得 %s err=%v", got.String(), err)
|
|
|
}
|
|
}
|
|
|
- // ⑤ 他机构等值:inst=2 → 50.00(越权防线已按裁定移到 Handler 角色白名单,仓储不再拦)
|
|
|
|
|
|
|
+ // ⑤ 他机构等值:inst=2 且 campus=2 → pool4 = 50.00
|
|
|
|
|
+ // (越权防线已按裁定移到 Handler 角色白名单,仓储不再拦)
|
|
|
if got, err := repo.SumUsable(ctx, user, 2, 2); err != nil || got.Cents() != 5000 {
|
|
if got, err := repo.SumUsable(ctx, user, 2, 2); err != nil || got.Cents() != 5000 {
|
|
|
t.Fatalf("SumUsable(2,2) 应为 50.00,实得 %s err=%v", got.String(), err)
|
|
t.Fatalf("SumUsable(2,2) 应为 50.00,实得 %s err=%v", got.String(), err)
|
|
|
}
|
|
}
|
|
|
- // 参数防御:负数非法(0 才是"不筛")
|
|
|
|
|
|
|
+ // 参数防御:负数非法(0 是合法取值,语义见上)
|
|
|
if _, err := repo.SumUsable(ctx, user, -1, 2); !errors.Is(err, infraerrors.ErrBadParam) {
|
|
if _, err := repo.SumUsable(ctx, user, -1, 2); !errors.Is(err, infraerrors.ErrBadParam) {
|
|
|
t.Fatalf("institutionID<0 应 ErrBadParam,实得 %v", err)
|
|
t.Fatalf("institutionID<0 应 ErrBadParam,实得 %v", err)
|
|
|
}
|
|
}
|
|
|
|
|
+ if _, err := repo.SumUsable(ctx, user, 1, -1); !errors.Is(err, infraerrors.ErrBadParam) {
|
|
|
|
|
+ t.Fatalf("campusID<0 应 ErrBadParam,实得 %v", err)
|
|
|
|
|
+ }
|
|
|
if _, err := repo.SumUsable(ctx, "", 1, 2); !errors.Is(err, infraerrors.ErrBadParam) {
|
|
if _, err := repo.SumUsable(ctx, "", 1, 2); !errors.Is(err, infraerrors.ErrBadParam) {
|
|
|
t.Fatalf("空 user_id 应 ErrBadParam,实得 %v", err)
|
|
t.Fatalf("空 user_id 应 ErrBadParam,实得 %v", err)
|
|
|
}
|
|
}
|