diff --git a/src/services/scheduler.js b/src/services/scheduler.js index 131ee09..b03d649 100644 --- a/src/services/scheduler.js +++ b/src/services/scheduler.js @@ -60,7 +60,15 @@ export async function checkExpiringSubscriptions(env) { const now = getNowInTimezone(timezone); const normalizedHours = Array.isArray(config.NOTIFICATION_HOURS) - ? config.NOTIFICATION_HOURS.map((h) => String(h).padStart(2, '0')) + ? config.NOTIFICATION_HOURS + .map((h) => String(h).trim()) + .filter((h) => h.length > 0) + .map((h) => { + const up = h.toUpperCase(); + if (up === '*' || up === 'ALL') return '*'; + // 仅对纯数字做两位补齐;'*' 之类通配符保持原样 + return /^\d+$/.test(h) ? h.padStart(2, '0') : up; + }) : []; const inWindow = normalizedHours.length === 0 || diff --git a/tests/services/scheduler.test.js b/tests/services/scheduler.test.js index 0486942..0cee2fc 100644 --- a/tests/services/scheduler.test.js +++ b/tests/services/scheduler.test.js @@ -193,6 +193,35 @@ describe('scheduler v3 - 时区 + 通知时段', () => { expect(fetchSpy).toHaveBeenCalledTimes(1); // 只发了一次 }); + + it('场景5:NOTIFICATION_HOURS=["*"] 通配符不应被 padStart 误处理为 "0*"', async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-05-24T00:00:00.000Z')); + await setConfig({ + JWT_SECRET: 's', + TIMEZONE: 'Asia/Shanghai', + NOTIFICATION_HOURS: ['*'], + ENABLED_NOTIFIERS: ['telegram'], + TG_BOT_TOKEN: 'B', + TG_CHAT_ID: 'C' + }); + await subRepo.save(env, { + id: 's-wc', + name: 'WC', + isActive: true, + autoRenew: false, + expiryDate: '2026-05-25T03:00:00.000Z' + }); + await remindersRepo.replaceForSubscription(env, 's-wc', [ + remindersRepo.normalizeRule({ type: 'before_expiry', value: 1, unit: 'days' }) + ]); + + mockTelegramOk(); + const log = await checkExpiringSubscriptions(env); + expect(log.inWindow).toBe(true); + expect(log.configuredHours).toEqual(['*']); + expect(log.sentCount).toBe(1); + }); }); describe('scheduler v3 - 自动续订', () => { diff --git a/wrangler.dev.toml b/wrangler.dev.toml new file mode 100644 index 0000000..af2b450 --- /dev/null +++ b/wrangler.dev.toml @@ -0,0 +1,27 @@ +# 本地开发专用配置(v3) +# +# 用法:npx wrangler dev --config wrangler.dev.toml +# +# 与 wrangler.toml 的差异: +# - 内联了一个 KV 命名空间假 ID,让 miniflare 模拟 KV 时不再报"未绑定" +# - 不动主 wrangler.toml,避免与 npm run setup 自动写入的 KV 块冲突 +name = "subscription-manager-dev" +main = "src/index.js" +compatibility_date = "2024-09-23" +compatibility_flags = ["nodejs_compat"] + +# 本地用的占位 KV,miniflare 会自动用 .wrangler/state 下的本地 KV 文件 +[[kv_namespaces]] +binding = "SUBSCRIPTIONS_KV" +id = "local-dev-kv-placeholder" +preview_id = "local-dev-kv-preview" + +[assets] +directory = "./public" +binding = "ASSETS" + +[triggers] +crons = ["0 * * * *"] + +[vars] +ENVIRONMENT = "development"