82 lines
3.2 KiB
Go
82 lines
3.2 KiB
Go
package tasks
|
|
|
|
import (
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
_ "time/tzdata"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
)
|
|
|
|
func monthlyExpiryMigration(t *testing.T) string {
|
|
t.Helper()
|
|
data, err := os.ReadFile("../../resource/migrations/20261014_monthly_package_expiry_notice.sql")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return regexp.MustCompile(`(?m)--[^\r\n]*`).ReplaceAllString(string(data), "")
|
|
}
|
|
|
|
func TestMonthlyExpiryDeploymentSchedule(t *testing.T) {
|
|
sql := monthlyExpiryMigration(t)
|
|
matches := regexp.MustCompile(`'CRON_TZ=[^']+'`).FindString(sql)
|
|
if matches == "" {
|
|
t.Fatal("migration must specify cron expression")
|
|
}
|
|
parser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)
|
|
schedule, err := parser.Parse(strings.Trim(matches, "'"))
|
|
if err != nil {
|
|
t.Fatalf("deployment expression must parse with the real scheduler: %v", err)
|
|
}
|
|
for _, tc := range []struct{ start, expected string }{
|
|
{"2026-10-14T01:00:00Z", "2026-10-15T01:00:00Z"},
|
|
{"2026-10-15T01:00:00Z", "2026-11-15T01:00:00Z"},
|
|
{"2026-12-15T01:00:00Z", "2027-01-15T01:00:00Z"},
|
|
{"2028-02-15T01:00:00Z", "2028-03-15T01:00:00Z"},
|
|
} {
|
|
start, _ := time.Parse(time.RFC3339, tc.start)
|
|
expected, _ := time.Parse(time.RFC3339, tc.expected)
|
|
if got := schedule.Next(start); !got.Equal(expected) {
|
|
t.Errorf("from %s: got %s, want %s", tc.start, got, expected)
|
|
}
|
|
}
|
|
for _, alias := range []string{"ScanSimExpireCandidateWarning", "scan_sim_expire_candidate_warning"} {
|
|
if got := GetInnerTaskName(alias); got != "ScanSimExpireCandidateWarning" {
|
|
t.Errorf("existing alias %s no longer registered: %s", alias, got)
|
|
}
|
|
}
|
|
if got := GetInnerTaskName("NotifyMonthlyPackageExpiry"); got != "NotifyMonthlyPackageExpiry" {
|
|
t.Errorf("monthly task not registered independently: %s", got)
|
|
}
|
|
if got, want := jobLockKey("NotifyMonthlyPackageExpiry", time.Date(2026, 10, 15, 9, 0, 0, 0, time.UTC), monthlyExpiryJobPeriod), jobLockKey("scan_sim_expire_candidate_warning", time.Date(2026, 10, 15, 9, 0, 0, 0, time.UTC), warningExpireScanJobPeriod); got == want {
|
|
t.Fatal("monthly and legacy tasks must not share the same lock")
|
|
}
|
|
}
|
|
|
|
// 只检查迁移脚本结构,不连接数据库或执行 SQL;不替代部署前的 MySQL 验证。
|
|
func TestMonthlyExpiryDeploymentInsertOnly(t *testing.T) {
|
|
sql := strings.Join(strings.Fields(monthlyExpiryMigration(t)), " ")
|
|
if strings.Count(strings.ToUpper(sql), "INSERT INTO SYS_JOB") != 1 {
|
|
t.Fatal("migration must insert only one independent sys_job")
|
|
}
|
|
if regexp.MustCompile(`(?i)\bUPDATE\s+sys_job\b|\bDELETE\s+FROM\s+sys_job\b|\bjob_id\b`).MatchString(sql) {
|
|
t.Fatal("migration must not update/delete jobs or provide a generated job_id")
|
|
}
|
|
for _, required := range []string{
|
|
"'NotifyMonthlyPackageExpiry'",
|
|
"'CRON_TZ=Asia/Shanghai 0 0 9 15 * *'",
|
|
"1, 1, '1'",
|
|
"WHERE NOT EXISTS ( SELECT 1 FROM sys_job WHERE invoke_target = 'NotifyMonthlyPackageExpiry' )",
|
|
} {
|
|
if !strings.Contains(sql, required) {
|
|
t.Errorf("migration missing required independent paused job behavior: %q", required)
|
|
}
|
|
}
|
|
if strings.Contains(sql, "ScanSimExpireCandidateWarning") || strings.Contains(sql, "scan_sim_expire_candidate_warning") {
|
|
t.Fatal("migration must not touch legacy job targets")
|
|
}
|
|
}
|