From 9741721b1b6ba11144812a342b57435800972c31 Mon Sep 17 00:00:00 2001 From: xuans Date: Wed, 30 Sep 2026 05:37:09 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=E8=AF=81=E4=B9=A6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=85=A5=E5=8F=A3=E6=A0=A1=E9=AA=8C=E4=B8=8E=E5=9B=9E?= =?UTF-8?q?=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 增加证书导入、恢复、校验任务入口的任务中心回归覆盖,并在任务创建前拒绝无效执行配置,避免异常任务落库。 --- backend/internal/app/certificate_tasks.go | 15 +++- .../internal/app/certificate_tasks_test.go | 84 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/backend/internal/app/certificate_tasks.go b/backend/internal/app/certificate_tasks.go index 461ed83..442b9f2 100644 --- a/backend/internal/app/certificate_tasks.go +++ b/backend/internal/app/certificate_tasks.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "strings" "time" "git.pcux.cn/xuans/Scert/backend/internal/certificate" @@ -60,9 +61,21 @@ func (w *certificateTaskWorkflow) Validate(ctx context.Context, domainID string) } func (w *certificateTaskWorkflow) run(ctx context.Context, id, taskType, stage string, attempts int, interval time.Duration, execute func(context.Context, string) (CertificateWorkflowResult, error)) (result CertificateWorkflowResult, resultErr error) { - if w.tasks == nil || w.audit == nil { + if w == nil || w.tasks == nil || w.audit == nil { return result, errors.New("证书任务记录服务未初始化") } + if strings.TrimSpace(id) == "" { + return result, errors.New("证书任务域名 ID 不能为空") + } + if strings.TrimSpace(taskType) == "" || strings.TrimSpace(stage) == "" { + return result, errors.New("证书任务类型和阶段不能为空") + } + if attempts < 1 { + return result, errors.New("证书任务至少执行一次") + } + if execute == nil { + return result, errors.New("证书任务执行器未配置") + } if active, err := w.tasks.FindActiveTaskByDomain(ctx, id, taskType); err == nil { return result, fmt.Errorf("%w:%s", repository.ErrTaskAlreadyRunning, active.ID) } else if !errors.Is(err, sql.ErrNoRows) { diff --git a/backend/internal/app/certificate_tasks_test.go b/backend/internal/app/certificate_tasks_test.go index 8aa66bf..14eb81c 100644 --- a/backend/internal/app/certificate_tasks_test.go +++ b/backend/internal/app/certificate_tasks_test.go @@ -218,6 +218,90 @@ func TestCertificateTaskEntryPointsRecordRenewalAndDeployment(t *testing.T) { } } +type restoringWorkflowCertificateService struct { + workflowCertificateService + restored certificate.IssueResult +} + +func (s *restoringWorkflowCertificateService) RestoreVersion(context.Context, string, string) (certificate.IssueResult, error) { + s.called = true + return s.restored, nil +} + +func TestCertificateTaskEntryPointsRecordImportRestoreAndValidate(t *testing.T) { + t.Run("import", func(t *testing.T) { + w, a := newCertificateTaskFixture(t) + service := &workflowCertificateService{result: certificate.IssueResult{ + DomainID: "task-domain", + Domains: []string{"task.example.test"}, + Saved: certificate.SavedResource{CertificatePath: "cert.pem"}, + }} + w.workflow = &CertificateWorkflow{ + Certificates: service, + Domains: &workflowDomainState{}, + Servers: workflowServerLister{servers: []deployment.Server{{ID: "server"}}}, + Deployment: &workflowDeployer{report: deployment.Report{Total: 1, Success: 1, Status: "success"}}, + } + _, err := w.Import(t.Context(), certificate.ImportRequest{DomainID: "task-domain", Certificate: "cert.pem", PrivateKey: "key.pem"}) + if err != nil || !service.called { + t.Fatalf("证书导入任务入口执行错误:%v", err) + } + item := assertCertificateTask(t, a, "success", "success") + if item.TaskType != "certificate_import" { + t.Fatalf("证书导入任务类型错误:%+v", item) + } + }) + + t.Run("restore", func(t *testing.T) { + w, a := newCertificateTaskFixture(t) + service := &restoringWorkflowCertificateService{workflowCertificateService: workflowCertificateService{ + result: certificate.IssueResult{DomainID: "task-domain"}, + }, restored: certificate.IssueResult{DomainID: "task-domain"}} + w.workflow = &CertificateWorkflow{Certificates: service} + _, err := w.Restore(t.Context(), "task-domain", "version-1") + if err != nil || !service.called { + t.Fatalf("证书恢复任务入口执行错误:%v", err) + } + item := assertCertificateTask(t, a, "success", "success") + if item.TaskType != "certificate_restore" { + t.Fatalf("证书恢复任务类型错误:%+v", item) + } + }) + + t.Run("validate", func(t *testing.T) { + w, a := newCertificateTaskFixture(t) + service := &workflowCertificateService{} + w.workflow = &CertificateWorkflow{ + CurrentCertificate: func(context.Context, string) (certificate.IssueResult, error) { + service.called = true + return certificate.IssueResult{DomainID: "task-domain"}, nil + }, + } + _, _, err := w.Validate(t.Context(), "task-domain") + if err == nil || !service.called { + t.Fatal("证书校验任务入口未返回预期失败") + } + item := assertCertificateTask(t, a, "failed", "failed") + if item.TaskType != "certificate_validate" { + t.Fatalf("证书校验任务类型错误:%+v", item) + } + }) +} + +func TestCertificateTaskRejectsInvalidExecutionConfiguration(t *testing.T) { + w, a := newCertificateTaskFixture(t) + _, err := w.run(t.Context(), "task-domain", "certificate_validate", "certificate_validate", 0, 0, func(context.Context, string) (CertificateWorkflowResult, error) { + return CertificateWorkflowResult{}, nil + }) + if err == nil || !strings.Contains(err.Error(), "至少执行一次") { + t.Fatalf("无效执行次数未被拒绝:%v", err) + } + items, listErr := a.Tasks.ListTasks(t.Context(), "task-domain", 10) + if listErr != nil || len(items) != 0 { + t.Fatalf("无效任务配置不应创建任务:%+v,%v", items, listErr) + } +} + type failingCertificateAudit struct { repository.AuditRepository failStatus string