发布 0.2.0:模型池自愈、探测修正、任务异步触发与删除加固
CI / test (push) Successful in 32s
Release / release (push) Successful in 52s

This commit is contained in:
2026-07-10 20:25:37 +08:00
parent dbba1f4905
commit 7706f59549
19 changed files with 978 additions and 107 deletions
+68 -1
View File
@@ -108,7 +108,10 @@ func tenantTaskCases() []tenantTaskCase {
{name: "测活多租户", task: taskOf(model.TaskTypeHealthCheck, `{"ociConfigIds":[1,2]}`), wantOK: true, wantPayload: `{"ociConfigIds":[2]}`},
{name: "成本去重命中", task: taskOf(model.TaskTypeCost, `{"ociConfigIds":[1,1,2]}`), wantOK: true, wantPayload: `{"ociConfigIds":[2]}`},
{name: "成本未命中", task: taskOf(model.TaskTypeCost, `{"ociConfigIds":[2]}`)},
{name: "非法 JSON", task: taskOf(model.TaskTypeCost, `{`), wantErr: true},
// 坏 payload 记警告跳过(保留原任务),不再 fail-closed 阻断整个租户删除
{name: "非法 JSON 跳过", task: taskOf(model.TaskTypeCost, `{`)},
{name: "抢机坏 payload 跳过", task: taskOf(model.TaskTypeSnatch, `not-json`)},
{name: "抢机空 payload 跳过", task: taskOf(model.TaskTypeSnatch, ``)},
}
}
@@ -607,3 +610,67 @@ func assertCount(t *testing.T, db *gorm.DB, value any, query string, args []any,
t.Errorf("count %T = %d, want %d", value, got, want)
}
}
func TestDeleteTenantSkipsCorruptTaskPayload(t *testing.T) {
configs, _, db := newTenantDeleteEnv(t, &fakeClient{})
target, _ := seedDeleteTenants(t, db)
corrupt := model.Task{Name: "corrupt-snatch", Type: model.TaskTypeSnatch, Payload: `{broken`}
mustCreate(t, db, &corrupt)
mustCreate(t, db, &model.TaskLog{TaskID: corrupt.ID, Message: "keep"})
if err := configs.Delete(context.Background(), target.ID); err != nil {
t.Fatalf("坏 payload 不应阻断租户删除: %v", err)
}
var kept model.Task
if err := db.First(&kept, corrupt.ID).Error; err != nil || kept.Payload != `{broken` {
t.Errorf("坏任务应原样保留: %+v, %v", kept, err)
}
assertCount(t, db, &model.TaskLog{}, "task_id = ?", []any{corrupt.ID}, 1)
assertCount(t, db, &model.OciConfig{}, "id = ?", []any{target.ID}, 0)
}
func TestDeleteTenantEventsAffectedRules(t *testing.T) {
_, _, db := newTenantDeleteEnv(t, &fakeClient{})
target, other := seedDeleteTenants(t, db)
seedTenantEvents(t, db, target.ID, other.ID)
// 规则 ID 须在删除前取:target-rule 会随租户一并删除
var targetRule, globalRule model.AlertRule
db.Where("name = ?", "target-rule").First(&targetRule)
db.Where("name = ?", "global-rule").First(&globalRule)
result := &tenantDeleteResult{}
err := db.Transaction(func(tx *gorm.DB) error {
return deleteTenantEvents(tx, target.ID, result)
})
if err != nil {
t.Fatalf("deleteTenantEvents: %v", err)
}
// target-rule 属本租户,global-rule 的命中引用了本租户事件:都应回收冷却
got := map[uint]bool{}
for _, id := range result.alertRuleIDs {
got[id] = true
}
if len(got) != 2 || !got[targetRule.ID] || !got[globalRule.ID] {
t.Errorf("alertRuleIDs = %v, want 含 target-rule(%d) 与 global-rule(%d)", result.alertRuleIDs, targetRule.ID, globalRule.ID)
}
// 仅保留 global-rule × other-event 一条命中
assertCount(t, db, &model.AlertRuleHit{}, "", nil, 1)
}
func TestDeleteTenantManyEventsNoVarLimit(t *testing.T) {
// 回归:事件数超 SQLite 绑定变量上限(32766)时删除仍成功(旧实现 IN 展开必失败)
configs, _, db := newTenantDeleteEnv(t, &fakeClient{})
target, _ := seedDeleteTenants(t, db)
events := make([]model.LogEvent, 0, 33000)
for i := 0; i < 33000; i++ {
events = append(events, model.LogEvent{OciConfigID: target.ID, MessageID: fmt.Sprintf("bulk-%d", i)})
}
if err := db.CreateInBatches(&events, 500).Error; err != nil {
t.Fatalf("seed events: %v", err)
}
if err := configs.Delete(context.Background(), target.ID); err != nil {
t.Fatalf("数万事件时删除不应受绑定变量上限影响: %v", err)
}
assertCount(t, db, &model.LogEvent{}, "oci_config_id = ?", []any{target.ID}, 0)
}