@gotza02/sequential-thinking 2026.2.19 → 2026.2.21

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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/dist/codestore.d.ts +28 -0
  3. package/dist/graph.d.ts +60 -0
  4. package/dist/graph.js +19 -0
  5. package/dist/http-server.d.ts +2 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/dist/lib.d.ts +67 -0
  9. package/dist/lib.js +323 -83
  10. package/dist/notes.d.ts +25 -0
  11. package/dist/system_test.d.ts +1 -0
  12. package/dist/test_ts_req.d.ts +1 -0
  13. package/dist/tools/codestore.d.ts +3 -0
  14. package/dist/tools/coding.d.ts +3 -0
  15. package/dist/tools/coding.js +40 -3
  16. package/dist/tools/filesystem.d.ts +2 -0
  17. package/dist/tools/graph.d.ts +3 -0
  18. package/dist/tools/graph.js +18 -0
  19. package/dist/tools/human.d.ts +65 -0
  20. package/dist/tools/human.js +305 -0
  21. package/dist/tools/notes.d.ts +3 -0
  22. package/dist/tools/thinking.d.ts +3 -0
  23. package/dist/tools/thinking.js +137 -65
  24. package/dist/tools/web.d.ts +2 -0
  25. package/dist/utils.d.ts +32 -0
  26. package/package.json +3 -1
  27. package/dist/chaos.test.js +0 -72
  28. package/dist/codestore.test.js +0 -59
  29. package/dist/coding.test.js +0 -130
  30. package/dist/e2e.test.js +0 -122
  31. package/dist/filesystem.test.js +0 -189
  32. package/dist/graph.test.js +0 -150
  33. package/dist/graph_repro.test.js +0 -63
  34. package/dist/integration.test.js +0 -58
  35. package/dist/notes.test.js +0 -74
  36. package/dist/registration.test.js +0 -39
  37. package/dist/repro_dollar.js +0 -30
  38. package/dist/repro_dollar_simple.js +0 -22
  39. package/dist/repro_history.js +0 -41
  40. package/dist/repro_path.js +0 -17
  41. package/dist/repro_search.test.js +0 -79
  42. package/dist/repro_ts_req.js +0 -3
  43. package/dist/server.test.js +0 -127
  44. package/dist/stress.test.js +0 -68
  45. package/dist/utils.test.js +0 -40
  46. package/dist/verify_cache.test.js +0 -27
  47. package/dist/verify_edit.test.js +0 -66
  48. package/dist/verify_notes.test.js +0 -36
  49. package/dist/verify_viz.test.js +0 -25
  50. package/dist/web_fallback.test.js +0 -103
  51. package/dist/web_read.test.js +0 -60
@@ -1,127 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterAll } from 'vitest';
2
- import { SequentialThinkingServer } from './lib.js';
3
- import * as fs from 'fs';
4
- describe('SequentialThinkingServer', () => {
5
- let server;
6
- const testStoragePath = 'test_thoughts.json';
7
- beforeEach(() => {
8
- if (fs.existsSync(testStoragePath)) {
9
- fs.unlinkSync(testStoragePath);
10
- }
11
- server = new SequentialThinkingServer(testStoragePath);
12
- });
13
- afterAll(() => {
14
- if (fs.existsSync(testStoragePath)) {
15
- fs.unlinkSync(testStoragePath);
16
- }
17
- });
18
- it('should process a basic linear thought', async () => {
19
- const input = {
20
- thought: "First step",
21
- thoughtNumber: 1,
22
- totalThoughts: 3,
23
- nextThoughtNeeded: true,
24
- thoughtType: 'analysis'
25
- };
26
- const result = await server.processThought(input);
27
- expect(result.isError).toBeUndefined();
28
- const content = JSON.parse(result.content[0].text);
29
- expect(content.thoughtNumber).toBe(1);
30
- expect(content.thoughtHistoryLength).toBe(1);
31
- });
32
- it('should handle branching correctly', async () => {
33
- // Initial thought
34
- await server.processThought({
35
- thought: "Root thought",
36
- thoughtNumber: 1,
37
- totalThoughts: 3,
38
- nextThoughtNeeded: true
39
- });
40
- // Branch 1
41
- const branch1Input = {
42
- thought: "Alternative A",
43
- thoughtNumber: 2,
44
- totalThoughts: 3,
45
- nextThoughtNeeded: true,
46
- branchFromThought: 1,
47
- branchId: "branch-A",
48
- thoughtType: 'generation'
49
- };
50
- const result1 = await server.processThought(branch1Input);
51
- const content1 = JSON.parse(result1.content[0].text);
52
- expect(content1.branches).toContain("1-branch-A");
53
- // Branch 2
54
- const branch2Input = {
55
- thought: "Alternative B",
56
- thoughtNumber: 2,
57
- totalThoughts: 3,
58
- nextThoughtNeeded: true,
59
- branchFromThought: 1,
60
- branchId: "branch-B",
61
- thoughtType: 'generation'
62
- };
63
- const result2 = await server.processThought(branch2Input);
64
- const content2 = JSON.parse(result2.content[0].text);
65
- expect(content2.branches).toContain("1-branch-B");
66
- expect(content2.branches.length).toBe(2);
67
- });
68
- it('should handle evaluation with scores', async () => {
69
- const input = {
70
- thought: "Evaluating option X",
71
- thoughtNumber: 3,
72
- totalThoughts: 5,
73
- nextThoughtNeeded: true,
74
- thoughtType: 'evaluation',
75
- score: 8,
76
- options: ['Option X', 'Option Y']
77
- };
78
- const result = await server.processThought(input);
79
- expect(result.isError).toBeUndefined();
80
- // Since we don't return the score in the simple JSON response (only in logs or history),
81
- // we mainly check that it doesn't crash and processes correctly.
82
- // If we exposed history in the response, we could check that too.
83
- });
84
- it('should adjust totalThoughts if thoughtNumber exceeds it', async () => {
85
- const input = {
86
- thought: "Unexpected long process",
87
- thoughtNumber: 6,
88
- totalThoughts: 5,
89
- nextThoughtNeeded: true
90
- };
91
- const result = await server.processThought(input);
92
- const content = JSON.parse(result.content[0].text);
93
- expect(content.totalThoughts).toBe(6);
94
- });
95
- it('should clear thought history', async () => {
96
- await server.processThought({
97
- thought: "To be forgotten",
98
- thoughtNumber: 1,
99
- totalThoughts: 1,
100
- nextThoughtNeeded: false
101
- });
102
- await server.clearHistory();
103
- // Since we can't easily peek into private state, we'll process a new thought
104
- // and check if thoughtHistoryLength is 1 (meaning it started over or is just this one)
105
- const result = await server.processThought({
106
- thought: "Fresh start",
107
- thoughtNumber: 1,
108
- totalThoughts: 1,
109
- nextThoughtNeeded: false
110
- });
111
- const content = JSON.parse(result.content[0].text);
112
- expect(content.thoughtHistoryLength).toBe(1);
113
- });
114
- it('should summarize history correctly', async () => {
115
- // Add 3 thoughts
116
- await server.processThought({ thought: "T1", thoughtNumber: 1, totalThoughts: 3, nextThoughtNeeded: true });
117
- await server.processThought({ thought: "T2", thoughtNumber: 2, totalThoughts: 3, nextThoughtNeeded: true });
118
- await server.processThought({ thought: "T3", thoughtNumber: 3, totalThoughts: 3, nextThoughtNeeded: false });
119
- const result = await server.archiveHistory(1, 2, "Summary of T1 and T2");
120
- expect(result.newHistoryLength).toBe(2); // Summary + T3
121
- expect(result.summaryInsertedAt).toBe(1);
122
- });
123
- it('should throw error when summarizing invalid range', async () => {
124
- await server.processThought({ thought: "T1", thoughtNumber: 1, totalThoughts: 1, nextThoughtNeeded: false });
125
- await expect(server.archiveHistory(1, 5, "Invalid")).rejects.toThrow();
126
- });
127
- });
@@ -1,68 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { SequentialThinkingServer } from './lib.js';
3
- import { ProjectKnowledgeGraph } from './graph.js';
4
- import * as fs from 'fs/promises';
5
- import * as path from 'path';
6
- // Mock fs for graph test
7
- vi.mock('fs/promises', async (importOriginal) => {
8
- const actual = await importOriginal();
9
- return {
10
- ...actual,
11
- readdir: vi.fn(),
12
- readFile: vi.fn(),
13
- writeFile: vi.fn(),
14
- rename: vi.fn(),
15
- unlink: vi.fn()
16
- };
17
- });
18
- // Mock existsSync and readFileSync from 'fs' (non-promise) for SequentialThinkingServer
19
- vi.mock('fs', async () => {
20
- return {
21
- existsSync: () => false,
22
- readFileSync: () => '[]',
23
- unlinkSync: () => { }
24
- };
25
- });
26
- describe('Stress Testing', () => {
27
- it('should handle 1000 sequential thoughts', async () => {
28
- // We use a real instance but mocked fs
29
- const server = new SequentialThinkingServer('stress_thoughts.json');
30
- const startTime = Date.now();
31
- for (let i = 1; i <= 1000; i++) {
32
- await server.processThought({
33
- thought: `Thought ${i}`,
34
- thoughtNumber: i,
35
- totalThoughts: 1000,
36
- nextThoughtNeeded: i < 1000
37
- });
38
- }
39
- const duration = Date.now() - startTime;
40
- console.log(`Processed 1000 thoughts in ${duration}ms`);
41
- expect(duration).toBeLessThan(10000); // Should be fast enough (< 10s)
42
- });
43
- it('should handle large graph construction', async () => {
44
- const graph = new ProjectKnowledgeGraph();
45
- // Mock 1000 files
46
- const files = Array.from({ length: 1000 }, (_, i) => `file${i}.ts`);
47
- // Mock readdir to return these files (recursively? no, just flat for stress test)
48
- // logic in getAllFiles is recursive. We need to mock it to return all at once or handle recursion.
49
- // Actually, ProjectKnowledgeGraph.getAllFiles calls readdir.
50
- // Let's mock readdir to return files for root, and then nothing for subdirs.
51
- fs.readdir.mockImplementation(async (dir) => {
52
- if (dir === path.resolve('.')) {
53
- return files.map(f => ({
54
- name: f,
55
- isDirectory: () => false
56
- }));
57
- }
58
- return [];
59
- });
60
- fs.readFile.mockResolvedValue("import { x } from './file0';");
61
- const startTime = Date.now();
62
- const result = await graph.build('.');
63
- const duration = Date.now() - startTime;
64
- console.log(`Built graph of ${result.totalFiles} files in ${duration}ms`);
65
- expect(result.totalFiles).toBe(1000);
66
- expect(duration).toBeLessThan(5000);
67
- });
68
- });
@@ -1,40 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
- import { validatePath } from './utils.js';
3
- import * as path from 'path';
4
- describe('Utils: validatePath', () => {
5
- // Mock process.cwd to be a known fixed path
6
- const mockCwd = '/app/project';
7
- beforeEach(() => {
8
- vi.spyOn(process, 'cwd').mockReturnValue(mockCwd);
9
- });
10
- afterEach(() => {
11
- vi.restoreAllMocks();
12
- });
13
- it('should allow paths within the project root', () => {
14
- const p = validatePath('src/index.ts');
15
- expect(p).toBe(path.resolve(mockCwd, 'src/index.ts'));
16
- });
17
- it('should allow explicit ./ paths', () => {
18
- const p = validatePath('./package.json');
19
- expect(p).toBe(path.resolve(mockCwd, 'package.json'));
20
- });
21
- it('should block traversal to parent directory', () => {
22
- expect(() => {
23
- validatePath('../outside.txt');
24
- }).toThrow(/Access denied/);
25
- });
26
- it('should block multiple level traversal', () => {
27
- expect(() => {
28
- validatePath('src/../../etc/passwd');
29
- }).toThrow(/Access denied/);
30
- });
31
- it('should block absolute paths outside root', () => {
32
- // Only run this check if we can reliably simulate absolute paths
33
- // For now, let's assume standard unix paths for the test logic
34
- if (path.sep === '/') {
35
- expect(() => {
36
- validatePath('/etc/passwd');
37
- }).toThrow(/Access denied/);
38
- }
39
- });
40
- });
@@ -1,27 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { ProjectKnowledgeGraph } from './graph.js';
3
- import * as fs from 'fs/promises';
4
- import * as path from 'path';
5
- describe('Graph Caching Verification', () => {
6
- it('should use cache on second run', async () => {
7
- const graph = new ProjectKnowledgeGraph();
8
- const root = process.cwd();
9
- // Cleanup existing cache
10
- const cachePath = path.join(root, '.gemini_graph_cache.json');
11
- try {
12
- await fs.unlink(cachePath);
13
- }
14
- catch (e) { }
15
- console.log('--- Run 1 (Fresh) ---');
16
- const res1 = await graph.build(root);
17
- console.log('Result 1:', res1);
18
- expect(res1.parsedFiles).toBeGreaterThan(0);
19
- expect(res1.cachedFiles).toBe(0);
20
- console.log('--- Run 2 (Cached) ---');
21
- const res2 = await graph.build(root);
22
- console.log('Result 2:', res2);
23
- expect(res2.parsedFiles).toBe(0);
24
- expect(res2.cachedFiles).toBeGreaterThan(0);
25
- expect(res2.nodeCount).toBe(res1.nodeCount);
26
- });
27
- });
@@ -1,66 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs/promises';
3
- import * as path from 'path';
4
- // Replicating the logic from src/index.ts for testing
5
- async function editFileLogic(path, oldText, newText, allowMultiple = false) {
6
- try {
7
- const content = await fs.readFile(path, 'utf-8');
8
- const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\\]/g, '\\$&');
9
- const regex = new RegExp(escapeRegExp(oldText), 'g');
10
- const matchCount = (content.match(regex) || []).length;
11
- if (matchCount === 0) {
12
- return { error: "Error: 'oldText' not found" };
13
- }
14
- if (matchCount > 1 && !allowMultiple) {
15
- return { error: `Error: Found ${matchCount} occurrences` };
16
- }
17
- const newContent = content.replace(allowMultiple ? regex : oldText, newText);
18
- await fs.writeFile(path, newContent, 'utf-8');
19
- return { success: true };
20
- }
21
- catch (error) {
22
- return { error: String(error) };
23
- }
24
- }
25
- describe('edit_file logic', () => {
26
- const testFile = path.join(__dirname, 'test_edit.txt');
27
- beforeEach(async () => {
28
- await fs.writeFile(testFile, 'Line 1\nTarget\nLine 3\nTarget again');
29
- });
30
- afterEach(async () => {
31
- try {
32
- await fs.unlink(testFile);
33
- }
34
- catch { }
35
- });
36
- it('should replace single occurrence', async () => {
37
- await fs.writeFile(testFile, 'Line 1\nUnique\nLine 3');
38
- const result = await editFileLogic(testFile, 'Unique', 'Replaced');
39
- expect(result.error).toBeUndefined();
40
- const content = await fs.readFile(testFile, 'utf-8');
41
- expect(content).toContain('Line 1\nReplaced\nLine 3');
42
- });
43
- it('should fail if text not found', async () => {
44
- const result = await editFileLogic(testFile, 'Missing', 'New');
45
- expect(result.error).toContain("not found");
46
- });
47
- it('should fail if multiple found and allowMultiple=false', async () => {
48
- const result = await editFileLogic(testFile, 'Target', 'New');
49
- expect(result.error).toContain("Found 2 occurrences");
50
- });
51
- it('should replace multiple if allowMultiple=true', async () => {
52
- const result = await editFileLogic(testFile, 'Target', 'New', true);
53
- expect(result.error).toBeUndefined();
54
- const content = await fs.readFile(testFile, 'utf-8');
55
- expect(content).toBe('Line 1\nNew\nLine 3\nNew again');
56
- });
57
- it('should handle special regex characters in text', async () => {
58
- await fs.writeFile(testFile, 'func(a, b) { return a+b; }');
59
- const oldText = 'func(a, b) { return a+b; }';
60
- const newText = 'replacement';
61
- const result = await editFileLogic(testFile, oldText, newText);
62
- expect(result.error).toBeUndefined();
63
- const content = await fs.readFile(testFile, 'utf-8');
64
- expect(content).toBe('replacement');
65
- });
66
- });
@@ -1,36 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { NotesManager } from './notes.js';
3
- import * as fs from 'fs/promises';
4
- import * as path from 'path';
5
- describe('Notes Manager', () => {
6
- const testFile = path.join(__dirname, 'test_notes.json');
7
- let manager;
8
- beforeEach(async () => {
9
- manager = new NotesManager(testFile);
10
- });
11
- afterEach(async () => {
12
- try {
13
- await fs.unlink(testFile);
14
- }
15
- catch { }
16
- });
17
- it('should add and list notes', async () => {
18
- await manager.addNote("My Note", "Content", ["tag1"]);
19
- const notes = await manager.listNotes();
20
- expect(notes.length).toBe(1);
21
- expect(notes[0].title).toBe("My Note");
22
- });
23
- it('should search notes', async () => {
24
- await manager.addNote("React Tips", "Use hooks", ["react"]);
25
- await manager.addNote("Vue Tips", "Use composition", ["vue"]);
26
- const results = await manager.searchNotes("hooks");
27
- expect(results.length).toBe(1);
28
- expect(results[0].title).toBe("React Tips");
29
- });
30
- it('should delete note', async () => {
31
- const note = await manager.addNote("To Delete", "...");
32
- await manager.deleteNote(note.id);
33
- const notes = await manager.listNotes();
34
- expect(notes.length).toBe(0);
35
- });
36
- });
@@ -1,25 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { ProjectKnowledgeGraph } from './graph.js';
3
- import * as fs from 'fs/promises';
4
- import * as path from 'path';
5
- describe('Graph Visualization', () => {
6
- const testDir = path.join(__dirname, 'test_viz_env');
7
- const graph = new ProjectKnowledgeGraph();
8
- beforeEach(async () => {
9
- await fs.mkdir(testDir, { recursive: true });
10
- await fs.writeFile(path.join(testDir, 'a.ts'), 'import { b } from "./b";');
11
- await fs.writeFile(path.join(testDir, 'b.ts'), 'export const b = 1;');
12
- });
13
- afterEach(async () => {
14
- await fs.rm(testDir, { recursive: true, force: true });
15
- });
16
- it('should generate mermaid diagram', async () => {
17
- await graph.build(testDir);
18
- const mermaid = graph.toMermaid();
19
- console.log("Generated Mermaid:", mermaid);
20
- expect(mermaid).toContain('graph TD');
21
- expect(mermaid).toMatch(/N\d+\["a\.ts"\]/);
22
- expect(mermaid).toMatch(/N\d+\["b\.ts"\]/);
23
- expect(mermaid).toContain('-->');
24
- });
25
- });
@@ -1,103 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { registerWebTools } from './tools/web.js';
3
- import * as utils from './utils.js';
4
- // Mock utils
5
- vi.mock('./utils.js', async (importOriginal) => {
6
- const actual = await importOriginal();
7
- return {
8
- ...actual,
9
- fetchWithRetry: vi.fn(),
10
- };
11
- });
12
- describe('web_search fallback', () => {
13
- let mockToolCallback;
14
- const mockServer = {
15
- tool: vi.fn((name, desc, schema, callback) => {
16
- if (name === 'web_search') {
17
- mockToolCallback = callback;
18
- }
19
- })
20
- };
21
- const originalEnv = process.env;
22
- beforeEach(() => {
23
- process.env = { ...originalEnv };
24
- vi.clearAllMocks();
25
- });
26
- afterEach(() => {
27
- process.env = originalEnv;
28
- });
29
- it('should use Brave if configured and no provider specified', async () => {
30
- process.env.BRAVE_API_KEY = 'test-brave-key';
31
- delete process.env.EXA_API_KEY;
32
- delete process.env.GOOGLE_SEARCH_API_KEY;
33
- registerWebTools(mockServer);
34
- const mockResponse = {
35
- ok: true,
36
- json: async () => ({ web: { results: ['brave result'] } })
37
- };
38
- utils.fetchWithRetry.mockResolvedValue(mockResponse);
39
- const result = await mockToolCallback({ query: 'test' });
40
- expect(utils.fetchWithRetry).toHaveBeenCalledWith(expect.stringContaining('api.search.brave.com'), expect.anything());
41
- expect(result.isError).toBeUndefined();
42
- expect(JSON.parse(result.content[0].text)).toEqual(['brave result']);
43
- });
44
- it('should fallback to Exa if Brave fails', async () => {
45
- process.env.BRAVE_API_KEY = 'test-brave-key';
46
- process.env.EXA_API_KEY = 'test-exa-key';
47
- registerWebTools(mockServer);
48
- // First call (Brave) fails
49
- utils.fetchWithRetry
50
- .mockResolvedValueOnce({ ok: false, statusText: 'Brave Error', status: 500 })
51
- // Second call (Exa) succeeds
52
- .mockResolvedValueOnce({
53
- ok: true,
54
- json: async () => ({ results: ['exa result'] })
55
- });
56
- const result = await mockToolCallback({ query: 'test' });
57
- expect(utils.fetchWithRetry).toHaveBeenCalledTimes(2);
58
- // 1. Brave
59
- expect(utils.fetchWithRetry).toHaveBeenNthCalledWith(1, expect.stringContaining('api.search.brave.com'), expect.anything());
60
- // 2. Exa
61
- expect(utils.fetchWithRetry).toHaveBeenNthCalledWith(2, expect.stringContaining('api.exa.ai'), expect.anything());
62
- expect(result.isError).toBeUndefined();
63
- expect(JSON.parse(result.content[0].text)).toEqual(['exa result']);
64
- });
65
- it('should respect requested provider and verify its availability', async () => {
66
- process.env.BRAVE_API_KEY = 'test-brave-key';
67
- // Exa not configured
68
- delete process.env.EXA_API_KEY;
69
- registerWebTools(mockServer);
70
- const result = await mockToolCallback({ query: 'test', provider: 'exa' });
71
- expect(result.isError).toBe(true);
72
- expect(result.content[0].text).toContain("Requested provider 'exa' is not configured");
73
- });
74
- it('should try requested provider first, then fallback', async () => {
75
- process.env.BRAVE_API_KEY = 'test-brave-key';
76
- process.env.EXA_API_KEY = 'test-exa-key';
77
- registerWebTools(mockServer);
78
- // Request Exa
79
- // Mock Exa fail, Brave success
80
- utils.fetchWithRetry
81
- .mockResolvedValueOnce({ ok: false, statusText: 'Exa Error', status: 500 })
82
- .mockResolvedValueOnce({
83
- ok: true,
84
- json: async () => ({ web: { results: ['brave result'] } })
85
- });
86
- const result = await mockToolCallback({ query: 'test', provider: 'exa' });
87
- expect(utils.fetchWithRetry).toHaveBeenCalledTimes(2);
88
- // 1. Exa (requested)
89
- expect(utils.fetchWithRetry).toHaveBeenNthCalledWith(1, expect.stringContaining('api.exa.ai'), expect.anything());
90
- // 2. Brave (fallback)
91
- expect(utils.fetchWithRetry).toHaveBeenNthCalledWith(2, expect.stringContaining('api.search.brave.com'), expect.anything());
92
- expect(result.isError).toBeUndefined();
93
- });
94
- it('should return error if all fail', async () => {
95
- process.env.BRAVE_API_KEY = 'test-brave-key';
96
- registerWebTools(mockServer);
97
- utils.fetchWithRetry.mockResolvedValue({ ok: false, statusText: 'Some Error', status: 500 });
98
- const result = await mockToolCallback({ query: 'test' });
99
- expect(result.isError).toBe(true);
100
- expect(result.content[0].text).toContain("All search providers failed");
101
- expect(result.content[0].text).toContain("Brave API error");
102
- });
103
- });
@@ -1,60 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { registerWebTools } from './tools/web.js';
3
- import * as utils from './utils.js';
4
- vi.mock('./utils.js', async (importOriginal) => {
5
- const actual = await importOriginal();
6
- return {
7
- ...actual,
8
- fetchWithRetry: vi.fn(),
9
- validatePublicUrl: vi.fn(),
10
- };
11
- });
12
- describe('read_webpage tool', () => {
13
- let mockToolCallback;
14
- const mockServer = {
15
- tool: vi.fn((name, desc, schema, callback) => {
16
- if (name === 'read_webpage') {
17
- mockToolCallback = callback;
18
- }
19
- })
20
- };
21
- beforeEach(() => {
22
- vi.clearAllMocks();
23
- registerWebTools(mockServer);
24
- });
25
- it('should convert HTML to Markdown', async () => {
26
- const mockHtml = `
27
- <html>
28
- <head><title>Test Article</title></head>
29
- <body>
30
- <h1>Main Header</h1>
31
- <p>Paragraph <b>bold</b>.</p>
32
- </body>
33
- </html>
34
- `;
35
- utils.fetchWithRetry.mockResolvedValue({
36
- ok: true,
37
- text: async () => mockHtml
38
- });
39
- utils.validatePublicUrl.mockResolvedValue(undefined);
40
- const result = await mockToolCallback({ url: 'https://example.com' });
41
- expect(result.isError).toBeUndefined();
42
- const content = result.content[0].text;
43
- expect(content).toContain("Title: Test Article");
44
- expect(content).toContain("Main Header");
45
- expect(content).toContain("**bold**"); // Markdown bold
46
- });
47
- it('should handle private URL validation error', async () => {
48
- utils.validatePublicUrl.mockRejectedValue(new Error("Access denied: Private IP"));
49
- const result = await mockToolCallback({ url: 'http://localhost' });
50
- expect(result.isError).toBe(true);
51
- expect(result.content[0].text).toContain("Access denied");
52
- });
53
- it('should handle fetch errors', async () => {
54
- utils.validatePublicUrl.mockResolvedValue(undefined);
55
- utils.fetchWithRetry.mockRejectedValue(new Error("Network Error"));
56
- const result = await mockToolCallback({ url: 'https://example.com' });
57
- expect(result.isError).toBe(true);
58
- expect(result.content[0].text).toContain("Network Error");
59
- });
60
- });