114 lines
4.1 KiB
Go
114 lines
4.1 KiB
Go
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)
|
|
}
|
|
}
|