package jobs import ( "errors" "testing" "time" "gorm.io/driver/sqlite" "gorm.io/gorm" "go-admin/app/jobs/models" ) func jobExecutionTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatal(err) } if err := db.AutoMigrate(&models.SysJobExecutionLog{}); err != nil { t.Fatal(err) } return db } func TestRunWithExecutionLogRecordsSuccessAndSanitizedFailure(t *testing.T) { db := jobExecutionTestDB(t) core := JobCore{JobId: 7, Name: "测试任务", InvokeTarget: "TestTarget"} if err := runWithExecutionLog(db, core, func() error { return nil }); err != nil { t.Fatal(err) } rawSecret := "token=secret-value productSpec=private" if err := runWithExecutionLog(db, core, func() error { return errors.New(rawSecret) }); err == nil { t.Fatal("failed execution must return its error") } var records []models.SysJobExecutionLog if err := db.Order("id asc").Find(&records).Error; err != nil { t.Fatal(err) } if len(records) != 2 || records[0].Status != models.JobExecutionSucceeded || records[1].Status != models.JobExecutionFailed { t.Fatalf("unexpected records: %+v", records) } if records[1].ErrorCode != executionErrorExecFailed || records[1].ErrorMessage == rawSecret || records[1].ErrorMessage == "" { t.Fatalf("failure was not safely summarized: %+v", records[1]) } if records[0].FinishedAt == nil || records[1].FinishedAt == nil || records[0].ExecutionID == records[1].ExecutionID { t.Fatal("execution lifecycle or unique IDs were not recorded") } } func TestRecoverInterruptedExecutionLogs(t *testing.T) { db := jobExecutionTestDB(t) started := time.Now().UTC().Add(-2 * time.Second) record := models.SysJobExecutionLog{ ExecutionID: "00000000-0000-4000-8000-000000000010", JobID: 9, JobNameSnapshot: "中断任务", InvokeTargetSnapshot: "Interrupted", TriggerType: models.JobTriggerScheduled, Status: models.JobExecutionRunning, StartedAt: started, } if err := db.Create(&record).Error; err != nil { t.Fatal(err) } if err := RecoverInterruptedExecutionLogs(db); err != nil { t.Fatal(err) } if err := db.First(&record, record.ID).Error; err != nil { t.Fatal(err) } if record.Status != models.JobExecutionInterrupted || record.FinishedAt == nil || record.ErrorCode != executionErrorInterrupted || record.DurationMS < 1000 { t.Fatalf("record was not safely interrupted: %+v", record) } } func TestMissingExecutionTargetHasPublicSafeMessage(t *testing.T) { code, message := publicExecutionFailure(missingExecutionTarget("SecretTarget")) if code != executionErrorTargetMissing || message != "任务调用目标未注册" { t.Fatalf("unexpected target failure %s %s", code, message) } } func TestExecJobRecordsMissingTargetAsFailure(t *testing.T) { db := jobExecutionTestDB(t) previousJobList := jobList jobList = map[string]JobExec{} t.Cleanup(func() { jobList = previousJobList }) job := &ExecJob{JobCore: JobCore{JobId: 10, Name: "未注册任务", InvokeTarget: "MissingTarget"}, DB: db} job.Run() var record models.SysJobExecutionLog if err := db.First(&record).Error; err != nil { t.Fatal(err) } if record.Status != models.JobExecutionFailed || record.ErrorCode != executionErrorTargetMissing || record.ErrorMessage != "任务调用目标未注册" { t.Fatalf("missing target was not safely recorded: %+v", record) } } func TestRunWithExecutionLogSafelyRecordsPanic(t *testing.T) { db := jobExecutionTestDB(t) err := runWithExecutionLog(db, JobCore{JobId: 11, Name: "异常任务", InvokeTarget: "Panic"}, func() error { panic("provider-secret") }) if code, message := publicExecutionFailure(err); code != executionErrorPanicked || message != "任务执行异常中断" { t.Fatalf("panic was not returned as a safe failure: %s %s", code, message) } var record models.SysJobExecutionLog if err := db.First(&record).Error; err != nil { t.Fatal(err) } if record.Status != models.JobExecutionFailed || record.ErrorCode != executionErrorPanicked || record.ErrorMessage != "任务执行异常中断" { t.Fatalf("panic was not safely persisted: %+v", record) } }