sync: 同步 v1.0.4 并脱敏配置与镜像
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
package tasks
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAllFullSyncTaskRegistrations(t *testing.T) {
|
||||
for name, method := range map[string]string{
|
||||
"start_traffic_usage_full_sync": "StartTrafficUsageFullSync",
|
||||
"syns_sim_status_queue": "SynsSimStatusQueue",
|
||||
"syns_imei_queue": "SynsImeiQueue",
|
||||
"sync_traffic_usage_daily": "SyncTrafficUsageDaily",
|
||||
"sync_traffic_usage_monthly": "SyncTrafficUsageMonthly",
|
||||
"start_traffic_usage_monthly_history": "StartTrafficUsageMonthlyHistory",
|
||||
} {
|
||||
if got := GetInnerTaskName(name); got != method {
|
||||
t.Errorf("task %q registered as %q, want %q", name, got, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
)
|
||||
|
||||
const (
|
||||
monthlyExpiryJobPeriod = 24 * time.Hour
|
||||
monthlyExpiryJobTTL = 6 * time.Hour
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterInnerTask("NotifyMonthlyPackageExpiry", "NotifyMonthlyPackageExpiry")
|
||||
}
|
||||
|
||||
// NotifyMonthlyPackageExpiry uses an independent sys_job target and Redis lock.
|
||||
func (t Task) NotifyMonthlyPackageExpiry() {
|
||||
t.runWithRedisJobLock(context.Background(), redisJobLockOptions{
|
||||
InvokeTarget: "NotifyMonthlyPackageExpiry",
|
||||
Period: monthlyExpiryJobPeriod,
|
||||
TTL: monthlyExpiryJobTTL,
|
||||
}, service.SimWarning().NotifyMonthlyExpiry)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
warningTrafficJobPeriod = 15 * time.Minute
|
||||
warningTrafficJobPeriod = -time.Second
|
||||
warningTrafficJobTTL = 45 * time.Minute
|
||||
warningExpirePreloadJobPeriod = 24 * time.Hour
|
||||
warningExpirePreloadJobTTL = 6 * time.Hour
|
||||
@@ -42,7 +42,7 @@ func (t Task) ScanSimWarning() {
|
||||
|
||||
func (t Task) ScanSimTrafficWarning() {
|
||||
t.runWithRedisJobLock(context.Background(), redisJobLockOptions{
|
||||
InvokeTarget: "scan_sim_traffic_warning",
|
||||
InvokeTarget: "scan_sim_warning",
|
||||
Period: warningTrafficJobPeriod,
|
||||
TTL: warningTrafficJobTTL,
|
||||
}, service.SimWarning().ScanTraffic)
|
||||
@@ -50,7 +50,7 @@ func (t Task) ScanSimTrafficWarning() {
|
||||
|
||||
func (t Task) ScanSimFlowPoolWarning() {
|
||||
t.runWithRedisJobLock(context.Background(), redisJobLockOptions{
|
||||
InvokeTarget: "check_flow_pool_warning",
|
||||
InvokeTarget: "scan_sim_warning",
|
||||
Period: warningTrafficJobPeriod,
|
||||
TTL: warningTrafficJobTTL,
|
||||
}, service.SimWarning().ScanFlowPool)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTrafficUsageMonthlyTaskRegistrations(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"sync_traffic_usage_monthly": "SyncTrafficUsageMonthly",
|
||||
"start_traffic_usage_full_sync": "StartTrafficUsageFullSync",
|
||||
}
|
||||
for name, method := range tests {
|
||||
if actual := GetInnerTaskName(name); actual != method {
|
||||
t.Fatalf("task %q registered as %q, want %q", name, actual, method)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrafficUsageJobLockUsesStableActiveScope(t *testing.T) {
|
||||
first := time.Date(2026, 9, 20, 23, 59, 0, 0, time.Local)
|
||||
second := first.Add(2 * time.Minute)
|
||||
if got := jobLockPeriodKey(first, -1); got != "active" {
|
||||
t.Fatalf("first active key = %q", got)
|
||||
}
|
||||
if got := jobLockPeriodKey(second, -1); got != "active" {
|
||||
t.Fatalf("second active key = %q", got)
|
||||
}
|
||||
if jobLockKey("sync_traffic_usage_daily", first, -1) != jobLockKey("sync_traffic_usage_daily", second, -1) {
|
||||
t.Fatal("active job lock key must remain stable across midnight")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user