@ob1-sg/horizon 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +43 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +293 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +635 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/__tests__/attachment-downloader.test.d.ts +2 -0
- package/dist/lib/__tests__/attachment-downloader.test.d.ts.map +1 -0
- package/dist/lib/__tests__/attachment-downloader.test.js +163 -0
- package/dist/lib/__tests__/attachment-downloader.test.js.map +1 -0
- package/dist/lib/__tests__/cli-detection.test.d.ts +2 -0
- package/dist/lib/__tests__/cli-detection.test.d.ts.map +1 -0
- package/dist/lib/__tests__/cli-detection.test.js +119 -0
- package/dist/lib/__tests__/cli-detection.test.js.map +1 -0
- package/dist/lib/__tests__/config.test.d.ts +2 -0
- package/dist/lib/__tests__/config.test.d.ts.map +1 -0
- package/dist/lib/__tests__/config.test.js +291 -0
- package/dist/lib/__tests__/config.test.js.map +1 -0
- package/dist/lib/__tests__/gcp.test.d.ts +2 -0
- package/dist/lib/__tests__/gcp.test.d.ts.map +1 -0
- package/dist/lib/__tests__/gcp.test.js +104 -0
- package/dist/lib/__tests__/gcp.test.js.map +1 -0
- package/dist/lib/__tests__/git.test.d.ts +2 -0
- package/dist/lib/__tests__/git.test.d.ts.map +1 -0
- package/dist/lib/__tests__/git.test.js +62 -0
- package/dist/lib/__tests__/git.test.js.map +1 -0
- package/dist/lib/__tests__/linear-quick-check.test.d.ts +2 -0
- package/dist/lib/__tests__/linear-quick-check.test.d.ts.map +1 -0
- package/dist/lib/__tests__/linear-quick-check.test.js +152 -0
- package/dist/lib/__tests__/linear-quick-check.test.js.map +1 -0
- package/dist/lib/__tests__/loop-instance-name.test.d.ts +2 -0
- package/dist/lib/__tests__/loop-instance-name.test.d.ts.map +1 -0
- package/dist/lib/__tests__/loop-instance-name.test.js +90 -0
- package/dist/lib/__tests__/loop-instance-name.test.js.map +1 -0
- package/dist/lib/__tests__/output-logger.test.d.ts +2 -0
- package/dist/lib/__tests__/output-logger.test.d.ts.map +1 -0
- package/dist/lib/__tests__/output-logger.test.js +136 -0
- package/dist/lib/__tests__/output-logger.test.js.map +1 -0
- package/dist/lib/__tests__/prompts.test.d.ts +2 -0
- package/dist/lib/__tests__/prompts.test.d.ts.map +1 -0
- package/dist/lib/__tests__/prompts.test.js +70 -0
- package/dist/lib/__tests__/prompts.test.js.map +1 -0
- package/dist/lib/__tests__/provider.test.d.ts +2 -0
- package/dist/lib/__tests__/provider.test.d.ts.map +1 -0
- package/dist/lib/__tests__/provider.test.js +89 -0
- package/dist/lib/__tests__/provider.test.js.map +1 -0
- package/dist/lib/__tests__/rate-limit.test.d.ts +2 -0
- package/dist/lib/__tests__/rate-limit.test.d.ts.map +1 -0
- package/dist/lib/__tests__/rate-limit.test.js +275 -0
- package/dist/lib/__tests__/rate-limit.test.js.map +1 -0
- package/dist/lib/__tests__/readline.test.d.ts +2 -0
- package/dist/lib/__tests__/readline.test.d.ts.map +1 -0
- package/dist/lib/__tests__/readline.test.js +55 -0
- package/dist/lib/__tests__/readline.test.js.map +1 -0
- package/dist/lib/__tests__/stats-logger.test.d.ts +2 -0
- package/dist/lib/__tests__/stats-logger.test.d.ts.map +1 -0
- package/dist/lib/__tests__/stats-logger.test.js +297 -0
- package/dist/lib/__tests__/stats-logger.test.js.map +1 -0
- package/dist/lib/__tests__/update-checker.test.d.ts +2 -0
- package/dist/lib/__tests__/update-checker.test.d.ts.map +1 -0
- package/dist/lib/__tests__/update-checker.test.js +141 -0
- package/dist/lib/__tests__/update-checker.test.js.map +1 -0
- package/dist/lib/__tests__/version.test.d.ts +2 -0
- package/dist/lib/__tests__/version.test.d.ts.map +1 -0
- package/dist/lib/__tests__/version.test.js +51 -0
- package/dist/lib/__tests__/version.test.js.map +1 -0
- package/dist/lib/attachment-downloader.d.ts +26 -0
- package/dist/lib/attachment-downloader.d.ts.map +1 -0
- package/dist/lib/attachment-downloader.js +259 -0
- package/dist/lib/attachment-downloader.js.map +1 -0
- package/dist/lib/claude.d.ts +6 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +459 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/cli-detection.d.ts +25 -0
- package/dist/lib/cli-detection.d.ts.map +1 -0
- package/dist/lib/cli-detection.js +53 -0
- package/dist/lib/cli-detection.js.map +1 -0
- package/dist/lib/codex.d.ts +4 -0
- package/dist/lib/codex.d.ts.map +1 -0
- package/dist/lib/codex.js +320 -0
- package/dist/lib/codex.js.map +1 -0
- package/dist/lib/gcp.d.ts +21 -0
- package/dist/lib/gcp.d.ts.map +1 -0
- package/dist/lib/gcp.js +96 -0
- package/dist/lib/gcp.js.map +1 -0
- package/dist/lib/git.d.ts +3 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +24 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/init-project.d.ts +13 -0
- package/dist/lib/init-project.d.ts.map +1 -0
- package/dist/lib/init-project.js +420 -0
- package/dist/lib/init-project.js.map +1 -0
- package/dist/lib/linear-api.d.ts +32 -0
- package/dist/lib/linear-api.d.ts.map +1 -0
- package/dist/lib/linear-api.js +267 -0
- package/dist/lib/linear-api.js.map +1 -0
- package/dist/lib/linear-quick-check.d.ts +13 -0
- package/dist/lib/linear-quick-check.d.ts.map +1 -0
- package/dist/lib/linear-quick-check.js +61 -0
- package/dist/lib/linear-quick-check.js.map +1 -0
- package/dist/lib/loop-instance-name.d.ts +29 -0
- package/dist/lib/loop-instance-name.d.ts.map +1 -0
- package/dist/lib/loop-instance-name.js +105 -0
- package/dist/lib/loop-instance-name.js.map +1 -0
- package/dist/lib/output-logger.d.ts +23 -0
- package/dist/lib/output-logger.d.ts.map +1 -0
- package/dist/lib/output-logger.js +104 -0
- package/dist/lib/output-logger.js.map +1 -0
- package/dist/lib/prompts.d.ts +17 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +65 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/provider.d.ts +32 -0
- package/dist/lib/provider.d.ts.map +1 -0
- package/dist/lib/provider.js +27 -0
- package/dist/lib/provider.js.map +1 -0
- package/dist/lib/rate-limit.d.ts +14 -0
- package/dist/lib/rate-limit.d.ts.map +1 -0
- package/dist/lib/rate-limit.js +154 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/readline.d.ts +4 -0
- package/dist/lib/readline.d.ts.map +1 -0
- package/dist/lib/readline.js +39 -0
- package/dist/lib/readline.js.map +1 -0
- package/dist/lib/setup.d.ts +126 -0
- package/dist/lib/setup.d.ts.map +1 -0
- package/dist/lib/setup.js +482 -0
- package/dist/lib/setup.js.map +1 -0
- package/dist/lib/stats-logger.d.ts +92 -0
- package/dist/lib/stats-logger.d.ts.map +1 -0
- package/dist/lib/stats-logger.js +258 -0
- package/dist/lib/stats-logger.js.map +1 -0
- package/dist/lib/ui.d.ts +38 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +69 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/lib/update-checker.d.ts +17 -0
- package/dist/lib/update-checker.d.ts.map +1 -0
- package/dist/lib/update-checker.js +138 -0
- package/dist/lib/update-checker.js.map +1 -0
- package/dist/lib/version.d.ts +10 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +37 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { sleep, parseRateLimitReset, isRateLimitError, handleRateLimit, executeWithRateLimitRetry, } from '../rate-limit.js';
|
|
3
|
+
describe('rate-limit module', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.useRealTimers();
|
|
9
|
+
});
|
|
10
|
+
describe('sleep()', () => {
|
|
11
|
+
it('returns a promise that resolves after specified time', async () => {
|
|
12
|
+
const sleepPromise = sleep(1000);
|
|
13
|
+
vi.advanceTimersByTime(1000);
|
|
14
|
+
await expect(sleepPromise).resolves.toBeUndefined();
|
|
15
|
+
});
|
|
16
|
+
it('does not resolve before specified time', async () => {
|
|
17
|
+
let resolved = false;
|
|
18
|
+
sleep(1000).then(() => {
|
|
19
|
+
resolved = true;
|
|
20
|
+
});
|
|
21
|
+
vi.advanceTimersByTime(500);
|
|
22
|
+
expect(resolved).toBe(false);
|
|
23
|
+
vi.advanceTimersByTime(500);
|
|
24
|
+
await Promise.resolve(); // flush microtasks
|
|
25
|
+
expect(resolved).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('parseRateLimitReset()', () => {
|
|
29
|
+
it('parses "resets at 10:30 am (PST)" format', () => {
|
|
30
|
+
// Set system time to 10:00 AM PST (18:00 UTC) on a specific date
|
|
31
|
+
const now = new Date('2026-01-15T18:00:00.000Z');
|
|
32
|
+
vi.setSystemTime(now);
|
|
33
|
+
const result = parseRateLimitReset('Rate limit resets at 10:30 am (PST)');
|
|
34
|
+
// 10:30 AM PST = 18:30 UTC, which is 30 minutes from now + 1 minute buffer
|
|
35
|
+
expect(result).toBeGreaterThan(30 * 60 * 1000);
|
|
36
|
+
expect(result).toBeLessThan(32 * 60 * 1000);
|
|
37
|
+
});
|
|
38
|
+
it('parses "resets 12am (America/Los_Angeles)" format', () => {
|
|
39
|
+
// Set system time to 11:00 PM PST (07:00 UTC next day) on a specific date
|
|
40
|
+
const now = new Date('2026-01-15T07:00:00.000Z');
|
|
41
|
+
vi.setSystemTime(now);
|
|
42
|
+
const result = parseRateLimitReset('Your limit resets 12am (America/Los_Angeles)');
|
|
43
|
+
// 12:00 AM PST = 08:00 UTC, which is 1 hour from now + 1 minute buffer
|
|
44
|
+
expect(result).toBeGreaterThan(60 * 60 * 1000);
|
|
45
|
+
expect(result).toBeLessThan(62 * 60 * 1000);
|
|
46
|
+
});
|
|
47
|
+
it('handles 12-hour time with PM', () => {
|
|
48
|
+
const now = new Date('2026-01-15T18:00:00.000Z'); // 10:00 AM PST
|
|
49
|
+
vi.setSystemTime(now);
|
|
50
|
+
const result = parseRateLimitReset('Rate limit resets at 2:30 pm (PST)');
|
|
51
|
+
// 2:30 PM PST = 22:30 UTC, which is 4.5 hours from now
|
|
52
|
+
expect(result).toBeGreaterThan(4 * 60 * 60 * 1000);
|
|
53
|
+
expect(result).toBeLessThan(5 * 60 * 60 * 1000);
|
|
54
|
+
});
|
|
55
|
+
it('handles 12-hour time with AM (midnight case)', () => {
|
|
56
|
+
const now = new Date('2026-01-15T07:00:00.000Z'); // 11:00 PM PST
|
|
57
|
+
vi.setSystemTime(now);
|
|
58
|
+
const result = parseRateLimitReset('resets at 12:00 am (PST)');
|
|
59
|
+
// 12:00 AM PST = 08:00 UTC next day
|
|
60
|
+
expect(result).toBeGreaterThan(60 * 60 * 1000);
|
|
61
|
+
});
|
|
62
|
+
it('returns time in the future (rolls to next day if past)', () => {
|
|
63
|
+
// Set time to 11:00 AM PST (19:00 UTC)
|
|
64
|
+
const now = new Date('2026-01-15T19:00:00.000Z');
|
|
65
|
+
vi.setSystemTime(now);
|
|
66
|
+
const result = parseRateLimitReset('Rate limit resets at 10:30 am (PST)');
|
|
67
|
+
// 10:30 AM is in the past today, so should roll to tomorrow
|
|
68
|
+
// That's about 23.5 hours from now
|
|
69
|
+
expect(result).toBeGreaterThan(23 * 60 * 60 * 1000);
|
|
70
|
+
expect(result).toBeLessThan(24 * 60 * 60 * 1000);
|
|
71
|
+
});
|
|
72
|
+
it('handles string input', () => {
|
|
73
|
+
const now = new Date('2026-01-15T18:00:00.000Z');
|
|
74
|
+
vi.setSystemTime(now);
|
|
75
|
+
const result = parseRateLimitReset('Rate limit resets at 10:30 am (PST)');
|
|
76
|
+
expect(result).toBeGreaterThan(30 * 60 * 1000);
|
|
77
|
+
});
|
|
78
|
+
it('handles object input with `result` field', () => {
|
|
79
|
+
const now = new Date('2026-01-15T18:00:00.000Z');
|
|
80
|
+
vi.setSystemTime(now);
|
|
81
|
+
const result = parseRateLimitReset({
|
|
82
|
+
result: 'Rate limit resets at 10:30 am (PST)',
|
|
83
|
+
});
|
|
84
|
+
expect(result).toBeGreaterThan(30 * 60 * 1000);
|
|
85
|
+
});
|
|
86
|
+
it('handles object input with nested `message.content[0].text`', () => {
|
|
87
|
+
const now = new Date('2026-01-15T18:00:00.000Z');
|
|
88
|
+
vi.setSystemTime(now);
|
|
89
|
+
const result = parseRateLimitReset({
|
|
90
|
+
message: {
|
|
91
|
+
content: [{ text: 'Rate limit resets at 10:30 am (PST)' }],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
expect(result).toBeGreaterThan(30 * 60 * 1000);
|
|
95
|
+
});
|
|
96
|
+
it('returns default 5 minutes when no match', () => {
|
|
97
|
+
const result = parseRateLimitReset('Some unrelated error message');
|
|
98
|
+
expect(result).toBe(5 * 60 * 1000);
|
|
99
|
+
});
|
|
100
|
+
it('adds 1-minute buffer to calculated time', () => {
|
|
101
|
+
// Set time to exactly 10:00 AM PST (18:00 UTC)
|
|
102
|
+
const now = new Date('2026-01-15T18:00:00.000Z');
|
|
103
|
+
vi.setSystemTime(now);
|
|
104
|
+
const result = parseRateLimitReset('Rate limit resets at 10:30 am (PST)');
|
|
105
|
+
// 10:30 AM PST = 18:30 UTC = 30 minutes from now
|
|
106
|
+
// With 1 minute buffer = 31 minutes
|
|
107
|
+
expect(result).toBe(31 * 60 * 1000);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('isRateLimitError()', () => {
|
|
111
|
+
it('detects "rate limit" (case insensitive)', () => {
|
|
112
|
+
expect(isRateLimitError('You have hit a rate limit')).toBe(true);
|
|
113
|
+
expect(isRateLimitError('RATE LIMIT exceeded')).toBe(true);
|
|
114
|
+
expect(isRateLimitError('rate-limit error')).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it('detects "too many requests"', () => {
|
|
117
|
+
expect(isRateLimitError('Error: Too many requests')).toBe(true);
|
|
118
|
+
expect(isRateLimitError('too many requests, please slow down')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
it('detects "quota exceeded"', () => {
|
|
121
|
+
expect(isRateLimitError('Your API quota exceeded')).toBe(true);
|
|
122
|
+
expect(isRateLimitError('Quota Exceeded')).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
it('detects "usage limit"', () => {
|
|
125
|
+
expect(isRateLimitError('You hit your usage limit')).toBe(true);
|
|
126
|
+
expect(isRateLimitError('usage_limit reached')).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
it('detects "hit your limit" (Claude-specific)', () => {
|
|
129
|
+
expect(isRateLimitError("You've hit your limit for today")).toBe(true);
|
|
130
|
+
expect(isRateLimitError('hit your limit')).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('detects "RateLimitError" (Codex-specific)', () => {
|
|
133
|
+
expect(isRateLimitError('RateLimitError: API rate limit reached')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
it('detects "request limit reached"', () => {
|
|
136
|
+
expect(isRateLimitError('Request limit reached for the day')).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it('returns false for non-rate-limit errors', () => {
|
|
139
|
+
expect(isRateLimitError('Connection timeout')).toBe(false);
|
|
140
|
+
expect(isRateLimitError('Internal server error')).toBe(false);
|
|
141
|
+
expect(isRateLimitError('Authentication failed')).toBe(false);
|
|
142
|
+
expect(isRateLimitError('File not found')).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('handleRateLimit()', () => {
|
|
146
|
+
it('logs the wait time in minutes', async () => {
|
|
147
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
148
|
+
const handlePromise = handleRateLimit(3 * 60 * 1000);
|
|
149
|
+
vi.advanceTimersByTime(3 * 60 * 1000);
|
|
150
|
+
await handlePromise;
|
|
151
|
+
expect(consoleSpy).toHaveBeenCalledWith('Rate limited. Sleeping 3 minute(s)...');
|
|
152
|
+
consoleSpy.mockRestore();
|
|
153
|
+
});
|
|
154
|
+
it('calls sleep with correct duration', async () => {
|
|
155
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
156
|
+
const waitMs = 2 * 60 * 1000;
|
|
157
|
+
const handlePromise = handleRateLimit(waitMs);
|
|
158
|
+
// Verify it waits the full duration
|
|
159
|
+
vi.advanceTimersByTime(waitMs - 1);
|
|
160
|
+
// Promise should still be pending - we can't directly check this, but we can
|
|
161
|
+
// verify by advancing the remaining time and seeing it resolves
|
|
162
|
+
vi.advanceTimersByTime(1);
|
|
163
|
+
await handlePromise;
|
|
164
|
+
consoleSpy.mockRestore();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe('executeWithRateLimitRetry()', () => {
|
|
168
|
+
it('returns immediately if not rate limited', async () => {
|
|
169
|
+
const operation = vi.fn().mockResolvedValue({
|
|
170
|
+
rateLimited: false,
|
|
171
|
+
data: 'success',
|
|
172
|
+
});
|
|
173
|
+
const config = { maxRetries: 3 };
|
|
174
|
+
const result = await executeWithRateLimitRetry(operation, config, 'Test Agent');
|
|
175
|
+
expect(result.rateLimited).toBe(false);
|
|
176
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
177
|
+
});
|
|
178
|
+
it('retries on rate limit up to maxRetries', async () => {
|
|
179
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
180
|
+
let callCount = 0;
|
|
181
|
+
const operation = vi.fn().mockImplementation(async () => {
|
|
182
|
+
callCount++;
|
|
183
|
+
if (callCount < 3) {
|
|
184
|
+
return {
|
|
185
|
+
rateLimited: true,
|
|
186
|
+
retryAfterMs: 60000, // 1 minute
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return { rateLimited: false };
|
|
190
|
+
});
|
|
191
|
+
const config = { maxRetries: 3 };
|
|
192
|
+
const resultPromise = executeWithRateLimitRetry(operation, config, 'Test Agent');
|
|
193
|
+
// Use runAllTimersAsync to properly handle all timers and async operations
|
|
194
|
+
await vi.runAllTimersAsync();
|
|
195
|
+
const result = await resultPromise;
|
|
196
|
+
expect(result.rateLimited).toBe(false);
|
|
197
|
+
expect(operation).toHaveBeenCalledTimes(3);
|
|
198
|
+
consoleSpy.mockRestore();
|
|
199
|
+
});
|
|
200
|
+
it('returns after maxRetries exceeded', async () => {
|
|
201
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
202
|
+
const operation = vi.fn().mockResolvedValue({
|
|
203
|
+
rateLimited: true,
|
|
204
|
+
retryAfterMs: 60000,
|
|
205
|
+
});
|
|
206
|
+
const config = { maxRetries: 2 };
|
|
207
|
+
const resultPromise = executeWithRateLimitRetry(operation, config, 'Test Agent');
|
|
208
|
+
// First call - rate limited (attempt 1)
|
|
209
|
+
await Promise.resolve();
|
|
210
|
+
vi.advanceTimersByTime(60000);
|
|
211
|
+
// Second call - rate limited (attempt 2 = maxRetries)
|
|
212
|
+
await Promise.resolve();
|
|
213
|
+
const result = await resultPromise;
|
|
214
|
+
expect(result.rateLimited).toBe(true);
|
|
215
|
+
expect(operation).toHaveBeenCalledTimes(2);
|
|
216
|
+
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('max retries reached'));
|
|
217
|
+
consoleSpy.mockRestore();
|
|
218
|
+
});
|
|
219
|
+
it('uses retryAfterMs from result', async () => {
|
|
220
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
221
|
+
let callCount = 0;
|
|
222
|
+
const operation = vi.fn().mockImplementation(async () => {
|
|
223
|
+
callCount++;
|
|
224
|
+
if (callCount === 1) {
|
|
225
|
+
return {
|
|
226
|
+
rateLimited: true,
|
|
227
|
+
retryAfterMs: 120000, // 2 minutes
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
return { rateLimited: false };
|
|
231
|
+
});
|
|
232
|
+
const config = { maxRetries: 3 };
|
|
233
|
+
const resultPromise = executeWithRateLimitRetry(operation, config, 'Test Agent');
|
|
234
|
+
// First call - rate limited with 2 minute wait
|
|
235
|
+
await Promise.resolve();
|
|
236
|
+
// Should not proceed before 2 minutes
|
|
237
|
+
vi.advanceTimersByTime(60000);
|
|
238
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
239
|
+
// Advance remaining time
|
|
240
|
+
vi.advanceTimersByTime(60000);
|
|
241
|
+
const result = await resultPromise;
|
|
242
|
+
expect(result.rateLimited).toBe(false);
|
|
243
|
+
expect(operation).toHaveBeenCalledTimes(2);
|
|
244
|
+
consoleSpy.mockRestore();
|
|
245
|
+
});
|
|
246
|
+
it('defaults to 5 minutes if no retryAfterMs', async () => {
|
|
247
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
248
|
+
let callCount = 0;
|
|
249
|
+
const operation = vi.fn().mockImplementation(async () => {
|
|
250
|
+
callCount++;
|
|
251
|
+
if (callCount === 1) {
|
|
252
|
+
return {
|
|
253
|
+
rateLimited: true,
|
|
254
|
+
// No retryAfterMs
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return { rateLimited: false };
|
|
258
|
+
});
|
|
259
|
+
const config = { maxRetries: 3 };
|
|
260
|
+
const resultPromise = executeWithRateLimitRetry(operation, config, 'Test Agent');
|
|
261
|
+
// First call - rate limited with default 5 minute wait
|
|
262
|
+
await Promise.resolve();
|
|
263
|
+
// Should not proceed before 5 minutes
|
|
264
|
+
vi.advanceTimersByTime(4 * 60 * 1000);
|
|
265
|
+
expect(operation).toHaveBeenCalledTimes(1);
|
|
266
|
+
// Advance remaining time
|
|
267
|
+
vi.advanceTimersByTime(60000);
|
|
268
|
+
const result = await resultPromise;
|
|
269
|
+
expect(result.rateLimited).toBe(false);
|
|
270
|
+
expect(operation).toHaveBeenCalledTimes(2);
|
|
271
|
+
consoleSpy.mockRestore();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
//# sourceMappingURL=rate-limit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/rate-limit.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EACL,KAAK,EACL,mBAAmB,EACnB,gBAAgB,EAChB,eAAe,EACf,yBAAyB,GAG1B,MAAM,kBAAkB,CAAC;AAE1B,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE;QACvB,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,EAAE,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBACpB,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;YAC5B,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,mBAAmB;YAC5C,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,iEAAiE;YACjE,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,qCAAqC,CAAC,CAAC;YAC1E,2EAA2E;YAC3E,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;YAC3D,0EAA0E;YAC1E,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,8CAA8C,CAAC,CAAC;YACnF,uEAAuE;YACvE,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;YACtC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC,eAAe;YACjE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,oCAAoC,CAAC,CAAC;YACzE,uDAAuD;YACvD,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACnD,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC,eAAe;YACjE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,0BAA0B,CAAC,CAAC;YAC/D,oCAAoC;YACpC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;YAChE,uCAAuC;YACvC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,qCAAqC,CAAC,CAAC;YAC1E,4DAA4D;YAC5D,mCAAmC;YACnC,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;YAC9B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,qCAAqC,CAAC,CAAC;YAC1E,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;YAClD,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC;gBACjC,MAAM,EAAE,qCAAqC;aAC9C,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;YACpE,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC;gBACjC,OAAO,EAAE;oBACP,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,qCAAqC,EAAE,CAAC;iBAC3D;aACF,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,8BAA8B,CAAC,CAAC;YACnE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,+CAA+C;YAC/C,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC;YACjD,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;YAEtB,MAAM,MAAM,GAAG,mBAAmB,CAAC,qCAAqC,CAAC,CAAC;YAC1E,iDAAiD;YACjD,oCAAoC;YACpC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;QAClC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,gBAAgB,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjE,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3D,MAAM,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;YACrC,MAAM,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,CAAC,gBAAgB,CAAC,qCAAqC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;YAClC,MAAM,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;YAC/B,MAAM,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChE,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACpD,MAAM,CAAC,gBAAgB,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvE,MAAM,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,MAAM,CAAC,gBAAgB,CAAC,wCAAwC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;YACzC,MAAM,CAAC,gBAAgB,CAAC,mCAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;YACjD,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC3D,MAAM,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,CAAC,gBAAgB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC9D,MAAM,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAEzE,MAAM,aAAa,GAAG,eAAe,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACrD,EAAE,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACtC,MAAM,aAAa,CAAC;YAEpB,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,uCAAuC,CAAC,CAAC;YACjF,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACzE,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YAE7B,MAAM,aAAa,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YAE9C,oCAAoC;YACpC,EAAE,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACnC,6EAA6E;YAC7E,gEAAgE;YAChE,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,aAAa,CAAC;YAEpB,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,6BAA6B,EAAE,GAAG,EAAE;QAC3C,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;gBAC1C,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,SAAS;aAC0B,CAAC,CAAC;YAE7C,MAAM,MAAM,GAAyB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,MAAM,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAEhF,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACzE,IAAI,SAAS,GAAG,CAAC,CAAC;YAElB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;gBACtD,SAAS,EAAE,CAAC;gBACZ,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;oBAClB,OAAO;wBACL,WAAW,EAAE,IAAI;wBACjB,YAAY,EAAE,KAAK,EAAE,WAAW;qBACV,CAAC;gBAC3B,CAAC;gBACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAyB,CAAC;YACvD,CAAC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAyB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YAEvD,MAAM,aAAa,GAAG,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAEjF,2EAA2E;YAC3E,MAAM,EAAE,CAAC,iBAAiB,EAAE,CAAC;YAE7B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YAEnC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAE3C,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,mCAAmC,EAAE,KAAK,IAAI,EAAE;YACjD,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAEzE,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC;gBAC1C,WAAW,EAAE,IAAI;gBACjB,YAAY,EAAE,KAAK;aACG,CAAC,CAAC;YAE1B,MAAM,MAAM,GAAyB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YAEvD,MAAM,aAAa,GAAG,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAEjF,wCAAwC;YACxC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACxB,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAE9B,sDAAsD;YACtD,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YAEnC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAC3C,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,CACrC,MAAM,CAAC,gBAAgB,CAAC,qBAAqB,CAAC,CAC/C,CAAC;YAEF,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACzE,IAAI,SAAS,GAAG,CAAC,CAAC;YAElB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;gBACtD,SAAS,EAAE,CAAC;gBACZ,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;oBACpB,OAAO;wBACL,WAAW,EAAE,IAAI;wBACjB,YAAY,EAAE,MAAM,EAAE,YAAY;qBACZ,CAAC;gBAC3B,CAAC;gBACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAyB,CAAC;YACvD,CAAC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAyB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACvD,MAAM,aAAa,GAAG,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAEjF,+CAA+C;YAC/C,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACxB,sCAAsC;YACtC,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAE3C,yBAAyB;YACzB,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YAEnC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAE3C,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;YACxD,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YACzE,IAAI,SAAS,GAAG,CAAC,CAAC;YAElB,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;gBACtD,SAAS,EAAE,CAAC;gBACZ,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;oBACpB,OAAO;wBACL,WAAW,EAAE,IAAI;wBACjB,kBAAkB;qBACI,CAAC;gBAC3B,CAAC;gBACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAyB,CAAC;YACvD,CAAC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAyB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;YACvD,MAAM,aAAa,GAAG,yBAAyB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;YAEjF,uDAAuD;YACvD,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YACxB,sCAAsC;YACtC,EAAE,CAAC,mBAAmB,CAAC,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;YACtC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAE3C,yBAAyB;YACzB,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YAEnC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACvC,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;YAE3C,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readline.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/readline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
let answers = [];
|
|
3
|
+
const closeMock = vi.fn();
|
|
4
|
+
const createInterfaceMock = vi.fn(() => ({
|
|
5
|
+
question: (question, cb) => {
|
|
6
|
+
cb(answers.shift() ?? '');
|
|
7
|
+
},
|
|
8
|
+
close: closeMock,
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('readline', () => ({
|
|
11
|
+
createInterface: createInterfaceMock,
|
|
12
|
+
}));
|
|
13
|
+
describe('readline helpers', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.resetModules();
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
answers = [];
|
|
18
|
+
});
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.unstubAllGlobals();
|
|
21
|
+
});
|
|
22
|
+
it('prompt() trims input and closes the interface', async () => {
|
|
23
|
+
answers.push(' hello ');
|
|
24
|
+
const { prompt } = await import('../readline.js');
|
|
25
|
+
await expect(prompt('Question: ')).resolves.toBe('hello');
|
|
26
|
+
expect(createInterfaceMock).toHaveBeenCalledTimes(1);
|
|
27
|
+
expect(closeMock).toHaveBeenCalledTimes(1);
|
|
28
|
+
});
|
|
29
|
+
it('confirm() returns true for y/yes (case-insensitive)', async () => {
|
|
30
|
+
const { confirm } = await import('../readline.js');
|
|
31
|
+
answers.push('Y');
|
|
32
|
+
await expect(confirm('Proceed?')).resolves.toBe(true);
|
|
33
|
+
answers.push(' yes ');
|
|
34
|
+
await expect(confirm('Proceed?')).resolves.toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('confirm() returns false for other values', async () => {
|
|
37
|
+
const { confirm } = await import('../readline.js');
|
|
38
|
+
answers.push('n');
|
|
39
|
+
await expect(confirm('Proceed?')).resolves.toBe(false);
|
|
40
|
+
answers.push('nope');
|
|
41
|
+
await expect(confirm('Proceed?')).resolves.toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('selectFromList() retries on invalid input and returns the selected option', async () => {
|
|
44
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
45
|
+
// invalid -> valid (select option #2)
|
|
46
|
+
answers.push('0');
|
|
47
|
+
answers.push('2');
|
|
48
|
+
const { selectFromList } = await import('../readline.js');
|
|
49
|
+
await expect(selectFromList('Pick one:', ['a', 'b', 'c'])).resolves.toBe('b');
|
|
50
|
+
const combined = logSpy.mock.calls.map(([line]) => String(line)).join('\n');
|
|
51
|
+
expect(combined).toContain('Invalid selection. Please try again.');
|
|
52
|
+
expect(closeMock).toHaveBeenCalledTimes(2);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=readline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readline.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/readline.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzE,IAAI,OAAO,GAAa,EAAE,CAAC;AAE3B,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;AAC1B,MAAM,mBAAmB,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IACvC,QAAQ,EAAE,CAAC,QAAgB,EAAE,EAA4B,EAAE,EAAE;QAC3D,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5B,CAAC;IACD,KAAK,EAAE,SAAS;CACjB,CAAC,CAAC,CAAC;AAEJ,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACzB,eAAe,EAAE,mBAAmB;CACrC,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,YAAY,EAAE,CAAC;QAClB,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,GAAG,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,gBAAgB,EAAE,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAE1B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAClD,MAAM,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1D,MAAM,CAAC,mBAAmB,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEnD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEtD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAEnD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAEvD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,MAAM,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAErE,sCAAsC;QACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAElB,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAC1D,MAAM,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAE9E,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5E,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,sCAAsC,CAAC,CAAC;QACnE,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats-logger.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/stats-logger.test.ts"],"names":[],"mappings":""}
|