@ob1-sg/horizon 0.1.10 → 0.1.11
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 +629 -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 +358 -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 +285 -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 +123 -0
- package/dist/lib/setup.d.ts.map +1 -0
- package/dist/lib/setup.js +492 -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/update-checker.d.ts +17 -0
- package/dist/lib/update-checker.d.ts.map +1 -0
- package/dist/lib/update-checker.js +140 -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 +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
|
+
// Mock fs module
|
|
4
|
+
vi.mock('fs', () => ({
|
|
5
|
+
existsSync: vi.fn(),
|
|
6
|
+
readFileSync: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
// Mock config to avoid side effects
|
|
9
|
+
vi.mock('../../config.js', () => ({
|
|
10
|
+
getRepoRoot: () => '/mock/repo',
|
|
11
|
+
}));
|
|
12
|
+
describe('loadPrompt', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it('substitutes template variables', async () => {
|
|
17
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
18
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
19
|
+
mockExistsSync.mockReturnValue(true);
|
|
20
|
+
mockReadFileSync.mockReturnValue('Hello {{NAME}}, mode is {{MODE}}.');
|
|
21
|
+
const { loadPrompt } = await import('../prompts.js');
|
|
22
|
+
const result = loadPrompt('test', { NAME: 'World', MODE: 'pr' });
|
|
23
|
+
expect(result).toBe('Hello World, mode is pr.');
|
|
24
|
+
});
|
|
25
|
+
it('returns content unchanged when no variables provided', async () => {
|
|
26
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
27
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
28
|
+
mockExistsSync.mockReturnValue(true);
|
|
29
|
+
mockReadFileSync.mockReturnValue('No variables here.');
|
|
30
|
+
const { loadPrompt } = await import('../prompts.js');
|
|
31
|
+
const result = loadPrompt('test');
|
|
32
|
+
expect(result).toBe('No variables here.');
|
|
33
|
+
});
|
|
34
|
+
it('replaces multiple occurrences of same variable', async () => {
|
|
35
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
36
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
37
|
+
mockExistsSync.mockReturnValue(true);
|
|
38
|
+
mockReadFileSync.mockReturnValue('{{X}} and {{X}} again.');
|
|
39
|
+
const { loadPrompt } = await import('../prompts.js');
|
|
40
|
+
const result = loadPrompt('test', { X: 'value' });
|
|
41
|
+
expect(result).toBe('value and value again.');
|
|
42
|
+
});
|
|
43
|
+
it('throws error when prompt not found', async () => {
|
|
44
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
45
|
+
mockExistsSync.mockReturnValue(false);
|
|
46
|
+
const { loadPrompt } = await import('../prompts.js');
|
|
47
|
+
expect(() => loadPrompt('nonexistent')).toThrow('Prompt not found: nonexistent');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('loadPromptFragment', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
vi.clearAllMocks();
|
|
53
|
+
});
|
|
54
|
+
it('loads fragment content from fragments directory', async () => {
|
|
55
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
56
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
57
|
+
mockExistsSync.mockReturnValue(true);
|
|
58
|
+
mockReadFileSync.mockReturnValue('Fragment content here.');
|
|
59
|
+
const { loadPromptFragment } = await import('../prompts.js');
|
|
60
|
+
const result = loadPromptFragment('merge-direct');
|
|
61
|
+
expect(result).toBe('Fragment content here.');
|
|
62
|
+
});
|
|
63
|
+
it('throws error when fragment not found', async () => {
|
|
64
|
+
const mockExistsSync = vi.mocked(existsSync);
|
|
65
|
+
mockExistsSync.mockReturnValue(false);
|
|
66
|
+
const { loadPromptFragment } = await import('../prompts.js');
|
|
67
|
+
expect(() => loadPromptFragment('nonexistent')).toThrow('Prompt fragment not found: nonexistent');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=prompts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompts.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/prompts.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAE9C,iBAAiB;AACjB,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACnB,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;IACnB,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;CACtB,CAAC,CAAC,CAAC;AAEJ,oCAAoC;AACpC,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC;IAChC,WAAW,EAAE,GAAG,EAAE,CAAC,YAAY;CAChC,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,gBAAgB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAEjD,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,eAAe,CAAC,mCAAmC,CAAC,CAAC;QAEtE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,gBAAgB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAEjD,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,eAAe,CAAC,oBAAoB,CAAC,CAAC;QAEvD,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;QAElC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;QAC9D,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,gBAAgB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAEjD,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,eAAe,CAAC,wBAAwB,CAAC,CAAC;QAE3D,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAClD,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,cAAc,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAEtC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,MAAM,gBAAgB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAEjD,cAAc,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QACrC,gBAAgB,CAAC,eAAe,CAAC,wBAAwB,CAAC,CAAC;QAE3D,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAC7C,cAAc,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAEtC,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { registerClaudeProvider, registerCodexProvider, createProvider, } from '../provider.js';
|
|
3
|
+
describe('provider module', () => {
|
|
4
|
+
// Create mock providers for testing
|
|
5
|
+
const createMockProvider = (name) => ({
|
|
6
|
+
name,
|
|
7
|
+
spawn: async () => ({
|
|
8
|
+
output: '',
|
|
9
|
+
finalOutput: 'test',
|
|
10
|
+
rateLimited: false,
|
|
11
|
+
cost: 0,
|
|
12
|
+
costEstimated: false,
|
|
13
|
+
duration: 0,
|
|
14
|
+
exitCode: 0,
|
|
15
|
+
tokenUsage: { input: 0, output: 0, cached: 0 },
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
describe('registerClaudeProvider()', () => {
|
|
19
|
+
it('registers factory function', () => {
|
|
20
|
+
const mockClaudeProvider = createMockProvider('claude');
|
|
21
|
+
registerClaudeProvider(() => mockClaudeProvider);
|
|
22
|
+
const provider = createProvider('claude');
|
|
23
|
+
expect(provider.name).toBe('claude');
|
|
24
|
+
});
|
|
25
|
+
it('allows re-registration', () => {
|
|
26
|
+
const mockProvider1 = createMockProvider('claude');
|
|
27
|
+
const mockProvider2 = { ...createMockProvider('claude'), customField: 'test' };
|
|
28
|
+
registerClaudeProvider(() => mockProvider1);
|
|
29
|
+
registerClaudeProvider(() => mockProvider2);
|
|
30
|
+
const provider = createProvider('claude');
|
|
31
|
+
expect(provider.name).toBe('claude');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('registerCodexProvider()', () => {
|
|
35
|
+
it('registers factory function', () => {
|
|
36
|
+
const mockCodexProvider = createMockProvider('codex');
|
|
37
|
+
registerCodexProvider(() => mockCodexProvider);
|
|
38
|
+
const provider = createProvider('codex');
|
|
39
|
+
expect(provider.name).toBe('codex');
|
|
40
|
+
});
|
|
41
|
+
it('allows re-registration', () => {
|
|
42
|
+
const mockProvider1 = createMockProvider('codex');
|
|
43
|
+
const mockProvider2 = { ...createMockProvider('codex'), customField: 'test' };
|
|
44
|
+
registerCodexProvider(() => mockProvider1);
|
|
45
|
+
registerCodexProvider(() => mockProvider2);
|
|
46
|
+
const provider = createProvider('codex');
|
|
47
|
+
expect(provider.name).toBe('codex');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('createProvider()', () => {
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
// Register both providers for these tests
|
|
53
|
+
registerClaudeProvider(() => createMockProvider('claude'));
|
|
54
|
+
registerCodexProvider(() => createMockProvider('codex'));
|
|
55
|
+
});
|
|
56
|
+
it('returns Claude provider when registered', () => {
|
|
57
|
+
const provider = createProvider('claude');
|
|
58
|
+
expect(provider.name).toBe('claude');
|
|
59
|
+
expect(typeof provider.spawn).toBe('function');
|
|
60
|
+
});
|
|
61
|
+
it('returns Codex provider when registered', () => {
|
|
62
|
+
const provider = createProvider('codex');
|
|
63
|
+
expect(provider.name).toBe('codex');
|
|
64
|
+
expect(typeof provider.spawn).toBe('function');
|
|
65
|
+
});
|
|
66
|
+
it('throws for unknown provider name', () => {
|
|
67
|
+
expect(() => createProvider('unknown')).toThrow('Unknown provider: unknown');
|
|
68
|
+
});
|
|
69
|
+
it('creates new provider instance on each call', () => {
|
|
70
|
+
const provider1 = createProvider('claude');
|
|
71
|
+
const provider2 = createProvider('claude');
|
|
72
|
+
// Each call should create a new provider instance (factory pattern)
|
|
73
|
+
expect(provider1).not.toBe(provider2);
|
|
74
|
+
});
|
|
75
|
+
it('provider spawn returns expected result structure', async () => {
|
|
76
|
+
const provider = createProvider('claude');
|
|
77
|
+
const result = await provider.spawn({ prompt: 'test' });
|
|
78
|
+
expect(result).toHaveProperty('output');
|
|
79
|
+
expect(result).toHaveProperty('finalOutput');
|
|
80
|
+
expect(result).toHaveProperty('rateLimited');
|
|
81
|
+
expect(result).toHaveProperty('cost');
|
|
82
|
+
expect(result).toHaveProperty('tokenUsage');
|
|
83
|
+
expect(result.tokenUsage).toHaveProperty('input');
|
|
84
|
+
expect(result.tokenUsage).toHaveProperty('output');
|
|
85
|
+
expect(result.tokenUsage).toHaveProperty('cached');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=provider.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/provider.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/rate-limit.test.ts"],"names":[],"mappings":""}
|
|
@@ -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":"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"}
|
|
@@ -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":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stats-logger.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/stats-logger.test.ts"],"names":[],"mappings":""}
|