File size: 5,393 Bytes
f0634fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import { mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

import { describe, expect, it } from 'vitest';

import { resolveClockSources, SYSTEM_CLOCKS } from '#/features/cron/internal/clock';

describe('cron clock sources', () => {
  describe('SYSTEM_CLOCKS', () => {
    it('returns a non-decreasing monotonic clock', () => {
      let prev = SYSTEM_CLOCKS.monoNowMs();
      for (let i = 0; i < 1000; i++) {
        const next = SYSTEM_CLOCKS.monoNowMs();
        expect(next).toBeGreaterThanOrEqual(prev);
        prev = next;
      }
    });

    it('returns wall time close to Date.now()', () => {
      const before = Date.now();
      const sample = SYSTEM_CLOCKS.wallNow();
      const after = Date.now();
      expect(sample).toBeGreaterThanOrEqual(before);
      expect(sample).toBeLessThanOrEqual(after);
    });

    it('returns a finite positive monotonic value', () => {
      const sample = SYSTEM_CLOCKS.monoNowMs();
      expect(Number.isFinite(sample)).toBe(true);
      expect(sample).toBeGreaterThan(0);
    });
  });

  describe('resolveClockSources default and system specs', () => {
    it('returns SYSTEM_CLOCKS for an undefined spec', () => {
      expect(resolveClockSources(undefined)).toBe(SYSTEM_CLOCKS);
    });

    it('returns SYSTEM_CLOCKS for an empty spec', () => {
      expect(resolveClockSources('')).toBe(SYSTEM_CLOCKS);
    });

    it('returns SYSTEM_CLOCKS for the system spec', () => {
      expect(resolveClockSources('system')).toBe(SYSTEM_CLOCKS);
    });

    it('falls back to SYSTEM_CLOCKS for an unknown scheme', () => {
      expect(resolveClockSources('garbage:foo')).toBe(SYSTEM_CLOCKS);
      expect(resolveClockSources('foobar')).toBe(SYSTEM_CLOCKS);
    });
  });

  describe('resolveClockSources file specs', () => {
    it('reads the file first line on every wallNow call', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');

      writeFileSync(filePath, '1000\n', 'utf8');
      const clocks = resolveClockSources(`file:${filePath}`);
      expect(clocks.wallNow()).toBe(1000);

      writeFileSync(filePath, '2500', 'utf8');
      expect(clocks.wallNow()).toBe(2500);

      writeFileSync(filePath, '4242\ngarbage\n', 'utf8');
      expect(clocks.wallNow()).toBe(4242);
    });

    it('falls back to Date.now() when the file is missing', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'missing.txt');
      const clocks = resolveClockSources(`file:${filePath}`);

      const before = Date.now();
      const sample = clocks.wallNow();
      const after = Date.now();

      expect(sample).toBeGreaterThanOrEqual(before);
      expect(sample).toBeLessThanOrEqual(after);
    });

    it('falls back to Date.now() for unparseable content', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');
      writeFileSync(filePath, 'not-a-number\n', 'utf8');
      const clocks = resolveClockSources(`file:${filePath}`);

      const before = Date.now();
      const sample = clocks.wallNow();
      const after = Date.now();

      expect(sample).toBeGreaterThanOrEqual(before);
      expect(sample).toBeLessThanOrEqual(after);
    });

    it('falls back to Date.now() for an empty file', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');
      writeFileSync(filePath, '', 'utf8');
      const clocks = resolveClockSources(`file:${filePath}`);

      const before = Date.now();
      const sample = clocks.wallNow();
      const after = Date.now();

      expect(sample).toBeGreaterThanOrEqual(before);
      expect(sample).toBeLessThanOrEqual(after);
    });

    it('does not use the file source for monoNowMs', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');
      writeFileSync(filePath, '1000', 'utf8');
      const clocks = resolveClockSources(`file:${filePath}`);

      const a = clocks.monoNowMs();
      const b = clocks.monoNowMs();

      expect(a).not.toBe(1000);
      expect(b).toBeGreaterThanOrEqual(a);
    });

    it('falls back to SYSTEM_CLOCKS for an empty file path', () => {
      expect(resolveClockSources('file:')).toBe(SYSTEM_CLOCKS);
    });

    it('caps file reads at 64 bytes and parses the prefix', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');
      writeFileSync(filePath, `${'1234567890\n'}${'x'.repeat(10_000)}`, 'utf8');

      const clocks = resolveClockSources(`file:${filePath}`);

      expect(clocks.wallNow()).toBe(1234567890);
    });

    it('rejects garbage past the 64 byte cap and falls back to Date.now()', () => {
      const tmpDir = mkdtempSync(join(tmpdir(), 'kimi-cron-clock-'));
      const filePath = join(tmpDir, 'now.txt');
      writeFileSync(filePath, 'x'.repeat(100), 'utf8');
      const clocks = resolveClockSources(`file:${filePath}`);

      const before = Date.now();
      const sample = clocks.wallNow();
      const after = Date.now();

      expect(sample).toBeGreaterThanOrEqual(before);
      expect(sample).toBeLessThanOrEqual(after);
    });
  });
});