Files
iotlcm/admin-svr/server/internal/tasks/monthly_expiry_test.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")
}
}