Newer
Older
client_dwatch / lib / ds-agent / __tests__ / fake-relay.ts
/**
 * ルートのテスト用: 偽の時計で動く relay(指示書AY 段階7-4。★本物のタイマーで待たない★)。
 * `at(t)` で BASE から t 秒まで時計を進め、その間に期限の来たタイマーを順に発火させる。
 */
import { createRelay, DEFAULT_RELAY_LIMITS, type RelayEvent, type RelayLimits } from "@/lib/ds-agent/relay";

export const FAKE_BASE = Date.parse("2026-10-19T12:00:00+09:00");

export async function flush(): Promise<void> {
  for (let i = 0; i < 20; i++) await new Promise<void>((resolve) => setImmediate(resolve));
}

export function createFakeRelay(overrides: Partial<RelayLimits> = {}) {
  let nowMs = FAKE_BASE;
  let nextHandle = 1;
  const timers = new Map<number, { at: number; fn: () => void }>();
  const events: RelayEvent[] = [];
  const limits: RelayLimits = { ...DEFAULT_RELAY_LIMITS, ...overrides };
  const relay = createRelay({
    now: () => nowMs,
    setTimer: (fn, ms) => {
      const handle = nextHandle++;
      timers.set(handle, { at: nowMs + ms, fn });
      return handle;
    },
    clearTimer: (handle) => {
      timers.delete(handle as number);
    },
    limits,
    onEvent: (e) => events.push(e),
  });

  async function at(t: number): Promise<void> {
    const target = FAKE_BASE + Math.round(t * 1000);
    for (;;) {
      let next: [number, { at: number; fn: () => void }] | undefined;
      for (const entry of timers) {
        if (entry[1].at <= target && (!next || entry[1].at < next[1].at)) next = entry;
      }
      if (!next) break;
      timers.delete(next[0]);
      nowMs = Math.max(nowMs, next[1].at);
      next[1].fn();
      await flush();
    }
    nowMs = target;
    await flush();
  }

  return { relay, limits, events, at };
}