package notification import ( "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "git.pcux.cn/xuans/Scert/backend/internal/deployment" htmlparser "golang.org/x/net/html" ) func TestFailedTaskCannotHaveCompletedSummary(t *testing.T) { for _, status := range []string{"failed", "error", "unknown"} { t.Run(status, func(t *testing.T) { payload, err := New(newTestConfig("http://mail.example.test"), nil).buildPayload(Event{ Status: status, Domain: "example.test", Deployment: deployment.Report{Total: 2, Success: 2, Status: "success"}, }) if err != nil { t.Fatal(err) } for _, body := range []string{payload.Text, payload.HTML} { if strings.Contains(body, "已完成") { t.Fatal("失败或未知任务不能显示完成摘要") } } }) } } func TestRecoveryWarningSurvivesHiddenDetails(t *testing.T) { for _, report := range []deployment.Report{ {RecoveryStatus: "unconfirmed"}, {RecoveryStatus: "failed"}, {RecoveryStatus: "recovery_required"}, {Results: []deployment.Result{{Status: "failed", Message: "已恢复旧文件,但服务或站点恢复状态未确认,已保留部署锁"}}}, } { cfg := newTestConfig("http://mail.example.test") cfg.IncludeDeploymentDetails = false payload, err := New(cfg, nil).buildPayload(Event{Status: "failed", Domain: "example.test", Deployment: report}) if err != nil { t.Fatal(err) } for _, body := range []string{payload.Text, payload.HTML} { if !strings.Contains(body, "勿直接重试或删除锁") { t.Fatal("隐藏部署明细不能隐藏恢复安全提示") } } } } func TestRecoveryWarningUsesFailureCodes(t *testing.T) { for _, code := range []deployment.FailureCode{ deployment.FailureRollback, deployment.FailureRuntimeUnknown, deployment.FailureRecoveryPending, deployment.FailureRecoveryUnknown, deployment.FailureLock, deployment.FailureLockRelease, } { t.Run(string(code), func(t *testing.T) { cfg := newTestConfig("http://mail.example.test") cfg.IncludeDeploymentDetails = false payload, err := New(cfg, nil).buildPayload(Event{ Status: "failed", Domain: "example.test", Error: "primary failure", Deployment: deployment.Report{Results: []deployment.Result{ {Status: "failed", Message: "first failure"}, {Status: "failed", Message: "second failure"}, {Server: "hidden-server", Status: "failed", FailureCode: code}, }}, }) if err != nil { t.Fatal(err) } for _, body := range []string{payload.Text, payload.HTML} { if !strings.Contains(body, "勿直接重试或删除锁") || strings.Contains(body, "hidden-server") { t.Fatal("恢复警告必须独立展示且不能暴露已隐藏的服务器名称") } } }) } } func TestVerifiedRecoveryDoesNotRequireManualUnlock(t *testing.T) { event := Event{Status: "failed", Deployment: deployment.Report{ RecoveryStatus: "success", Results: []deployment.Result{{FailureCode: deployment.FailureRecoveryRestored}}, }} if recoveryWarning(event) != "" { t.Fatal("已确认恢复的任务不应提示恢复未确认") } event = Event{Status: "failed", Error: "已保留部署锁"} if recoveryWarning(event) == "" { t.Fatal("仅任务错误携带恢复信息时也应提示核查") } } func TestMailUsesCertificateDomainInsteadOfInternalID(t *testing.T) { for _, domains := range [][]string{{" ", "example.test"}, nil} { payload, err := New(newTestConfig("http://mail.example.test"), nil).buildPayload(Event{ Status: "success", DomainID: "internal-domain-id", Domains: domains, }) if err != nil { t.Fatal(err) } expected := "未指定域名" if len(domains) > 0 { expected = "example.test" } for _, body := range []string{payload.Subject, payload.Text, payload.HTML} { if strings.Contains(body, "internal-domain-id") || !strings.Contains(body, expected) { t.Fatal("用户邮件应显示证书域名而非内部记录编号") } } } } func TestAllMailTemplatesHaveValidBodyStyles(t *testing.T) { for _, eventType := range []string{"certificate_issue", "certificate_renewal", "management_renewal", "certificate_import", "unknown"} { for _, status := range []string{"success", "failed", "error"} { payload, err := New(newTestConfig("http://mail.example.test"), nil).buildPayload(Event{ Type: eventType, Status: status, Domain: "example.test", }) if err != nil { t.Fatal(err) } assertMailBodyStyle(t, payload.HTML) assertReferenceMailStyle(t, payload.HTML) } } assertMailBodyStyle(t, buildSecurityHTML("01234567")) assertReferenceMailStyle(t, buildSecurityHTML("01234567")) if strings.Contains(buildSecurityHTML(``), "