Ctrl K
ComunidadGuías…

backend-ts/src/logic-bug-review-2026-11-11.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * 2026-11-11 逻辑 BUG 审查——BUG-1 回归用例
 * (详见 docs/code-review/2026-11-11-logic-bug-review-01.md)。
 *
 * 原为 it.fails 存在性证明;`legacyMode: false` 落地后已转正为常规断言。
 * 另保留单方受限 expression 的回归,防止 AND 语义误伤常见的
 * `0 0 10 ? * MON-FRI` 形态。
 *
 * 原 BUG-5(channelAssistantReply 空正文不回落兜底)经复核回退:既有
 * citation-markers.spec.ts 已固化「content 为 null → 空串」契约,改兜底属产品取舍,
 * 不按 logic bug 修复;对应存在性用例已移除。
 */
import { describe, expect, it } from 'vitest';
import { buildCronPreview } from './schedule/scheduled-task.service.js';

// ---------------------------------------------------------------------------
// BUG-1(高)DOM+DOW 受限的 Spring cron 按 legacyMode 的 OR 语义触发
// 位置:backend-ts/src/schedule/scheduled-task.service.ts 全部 5 处
//       `new Cron(..., { ...CRON_OPTIONS })`;CRON_OPTIONS 含 timezone + legacyMode: false
// 契约:产品接受的 cron 方言是 Spring/Quartz(normalizeSpringCron 专门把 Spring 的
//       `?` 归一成 croner 的 `*`)。Spring 语义下 DOM 与 DOW **同时受限**时取交集
//       (两者都要满足);croner 默认 legacyMode=true 是 POSIX 的 OR 语义
//       (任一命中即触发)。
// 实测(node 直探):'0 0 12 1 1 1'(1 月 1 日且周一,Spring 下上次触发是
//       2024-01-01、下次是 2029-01-01)在修复前 nextRuns 从 2027-01-01(周五)
//       开始、并把 2027 年 1 月的每个周一全部列出——每周都会跑,与用户意图(四年
//       一次)严重偏离。
// 修复:5 处 Cron 构造统一使用 CRON_OPTIONS(legacyMode: false,croner 的 Quartz 语义)。
// ---------------------------------------------------------------------------
describe('BUG-1: DOM+DOW 受限的 Spring cron 按 OR 语义触发(应为 AND)', () => {
  it('0 0 12 1 1 1 的每次触发都必须是「1 月 1 日且周一」', () => {
    const preview = buildCronPreview('0 0 12 1 1 1', 5);
    expect(preview.valid).toBe(true);
    expect(preview.nextFireTimes.length).toBeGreaterThan(0);
    for (const fireTime of preview.nextFireTimes) {
      // AND 语义下每次触发同时满足:日期为 01-01,且星期一
      expect(fireTime.slice(5, 10), `触发点 ${fireTime} 不是 1 月 1 日`).toBe('01-01');
      const [y, m, d] = fireTime.split(' ')[0]!.split('-').map(Number);
      const dow = new Date(Date.UTC(y, m - 1, d)).getUTCDay();
      expect(dow, `触发点 ${fireTime} 不是周一(POSIX OR 语义把每个周一都排进来了)`).toBe(1);
    }
  });

  it('单方受限的 ? * MON-FRI 仍按周一至周五触发(AND 不误伤通配字段)', () => {
    const preview = buildCronPreview('0 0 10 ? * MON-FRI', 5);
    expect(preview.valid).toBe(true);
    expect(preview.nextFireTimes).toHaveLength(5);
    for (const fireTime of preview.nextFireTimes) {
      const [y, m, d] = fireTime.split(' ')[0]!.split('-').map(Number);
      const dow = new Date(Date.UTC(y, m - 1, d)).getUTCDay();
      expect(dow, `触发点 ${fireTime} 落在周末`).toBeGreaterThanOrEqual(1);
      expect(dow, `触发点 ${fireTime} 落在周末`).toBeLessThanOrEqual(5);
    }
  });
});