@synth-coder/memhub 0.2.3 → 0.2.5
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/.github/workflows/ci.yml +48 -12
- package/.github/workflows/release.yml +70 -0
- package/AGENTS.md +71 -73
- package/README.md +284 -195
- package/README.zh-CN.md +90 -30
- package/dist/src/cli/agents/claude-code.d.ts +5 -0
- package/dist/src/cli/agents/claude-code.d.ts.map +1 -0
- package/dist/src/cli/agents/claude-code.js +14 -0
- package/dist/src/cli/agents/claude-code.js.map +1 -0
- package/dist/src/cli/agents/cline.d.ts +5 -0
- package/dist/src/cli/agents/cline.d.ts.map +1 -0
- package/dist/src/cli/agents/cline.js +14 -0
- package/dist/src/cli/agents/cline.js.map +1 -0
- package/dist/src/cli/agents/cursor.d.ts +5 -0
- package/dist/src/cli/agents/cursor.d.ts.map +1 -0
- package/dist/src/cli/agents/cursor.js +14 -0
- package/dist/src/cli/agents/cursor.js.map +1 -0
- package/dist/src/cli/agents/factory-droid.d.ts +5 -0
- package/dist/src/cli/agents/factory-droid.d.ts.map +1 -0
- package/dist/src/cli/agents/factory-droid.js +14 -0
- package/dist/src/cli/agents/factory-droid.js.map +1 -0
- package/dist/src/cli/agents/gemini-cli.d.ts +5 -0
- package/dist/src/cli/agents/gemini-cli.d.ts.map +1 -0
- package/dist/src/cli/agents/gemini-cli.js +14 -0
- package/dist/src/cli/agents/gemini-cli.js.map +1 -0
- package/dist/src/cli/agents/index.d.ts +13 -0
- package/dist/src/cli/agents/index.d.ts.map +1 -0
- package/dist/src/cli/agents/index.js +27 -0
- package/dist/src/cli/agents/index.js.map +1 -0
- package/dist/src/cli/agents/windsurf.d.ts +5 -0
- package/dist/src/cli/agents/windsurf.d.ts.map +1 -0
- package/dist/src/cli/agents/windsurf.js +14 -0
- package/dist/src/cli/agents/windsurf.js.map +1 -0
- package/dist/src/cli/index.d.ts +8 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +168 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/init.d.ts +34 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +140 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/instructions.d.ts +29 -0
- package/dist/src/cli/instructions.d.ts.map +1 -0
- package/dist/src/cli/instructions.js +141 -0
- package/dist/src/cli/instructions.js.map +1 -0
- package/dist/src/cli/types.d.ts +22 -0
- package/dist/src/cli/types.d.ts.map +1 -0
- package/dist/src/cli/types.js +75 -0
- package/dist/src/cli/types.js.map +1 -0
- package/dist/src/contracts/mcp.js +34 -34
- package/dist/src/contracts/schemas.js.map +1 -1
- package/dist/src/server/mcp-server.d.ts.map +1 -1
- package/dist/src/server/mcp-server.js +7 -14
- package/dist/src/server/mcp-server.js.map +1 -1
- package/dist/src/services/embedding-service.d.ts.map +1 -1
- package/dist/src/services/embedding-service.js +1 -1
- package/dist/src/services/embedding-service.js.map +1 -1
- package/dist/src/services/memory-service.d.ts.map +1 -1
- package/dist/src/services/memory-service.js.map +1 -1
- package/dist/src/storage/markdown-storage.d.ts.map +1 -1
- package/dist/src/storage/markdown-storage.js +1 -1
- package/dist/src/storage/markdown-storage.js.map +1 -1
- package/dist/src/storage/vector-index.d.ts.map +1 -1
- package/dist/src/storage/vector-index.js +4 -5
- package/dist/src/storage/vector-index.js.map +1 -1
- package/docs/README.md +21 -0
- package/docs/mcp-tools.md +136 -0
- package/docs/user-guide.md +184 -0
- package/package.json +61 -59
- package/src/cli/agents/claude-code.ts +14 -0
- package/src/cli/agents/cline.ts +14 -0
- package/src/cli/agents/codex.ts +14 -0
- package/src/cli/agents/cursor.ts +14 -0
- package/src/cli/agents/factory-droid.ts +14 -0
- package/src/cli/agents/gemini-cli.ts +14 -0
- package/src/cli/agents/index.ts +36 -0
- package/src/cli/agents/windsurf.ts +14 -0
- package/src/cli/index.ts +192 -0
- package/src/cli/init.ts +218 -0
- package/src/cli/instructions.ts +156 -0
- package/src/cli/types.ts +112 -0
- package/src/contracts/index.ts +12 -12
- package/src/contracts/mcp.ts +223 -223
- package/src/contracts/schemas.ts +307 -307
- package/src/contracts/types.ts +410 -410
- package/src/index.ts +8 -8
- package/src/server/index.ts +5 -5
- package/src/server/mcp-server.ts +169 -186
- package/src/services/embedding-service.ts +114 -114
- package/src/services/index.ts +5 -5
- package/src/services/memory-service.ts +656 -663
- package/src/storage/frontmatter-parser.ts +243 -243
- package/src/storage/index.ts +6 -6
- package/src/storage/markdown-storage.ts +228 -236
- package/src/storage/vector-index.ts +159 -160
- package/src/utils/index.ts +5 -5
- package/src/utils/slugify.ts +63 -63
- package/test/cli/init.test.ts +402 -0
- package/test/contracts/schemas.test.ts +313 -313
- package/test/contracts/types.test.ts +21 -21
- package/test/frontmatter-parser-more.test.ts +94 -94
- package/test/server/mcp-server.test.ts +211 -210
- package/test/services/memory-service-edge.test.ts +248 -248
- package/test/services/memory-service.test.ts +291 -279
- package/test/storage/frontmatter-parser.test.ts +223 -223
- package/test/storage/markdown-storage.test.ts +226 -217
- package/test/storage/storage-edge.test.ts +238 -238
- package/test/storage/vector-index.test.ts +149 -153
- package/test/utils/slugify-edge.test.ts +94 -94
- package/test/utils/slugify.test.ts +72 -68
- package/docs/architecture-diagrams.md +0 -368
- package/docs/architecture.md +0 -381
- package/docs/contracts.md +0 -190
- package/docs/prompt-template.md +0 -33
- package/docs/proposals/mcp-typescript-sdk-refactor.md +0 -568
- package/docs/proposals/proposal-close-gates.md +0 -58
- package/docs/tool-calling-policy.md +0 -101
- package/docs/vector-search.md +0 -306
|
@@ -1,238 +1,238 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Storage Edge Case Tests
|
|
3
|
-
* Additional tests for better coverage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
8
|
-
import { tmpdir } from 'os';
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
import { MarkdownStorage, StorageError } from '../../src/storage/markdown-storage.js';
|
|
11
|
-
import { FrontMatterError } from '../../src/storage/frontmatter-parser.js';
|
|
12
|
-
|
|
13
|
-
describe('MarkdownStorage Edge Cases', () => {
|
|
14
|
-
let tempDir: string;
|
|
15
|
-
let storage: MarkdownStorage;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
tempDir = mkdtempSync(join(tmpdir(), 'memhub-storage-edge-'));
|
|
19
|
-
storage = new MarkdownStorage({ storagePath: tempDir });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('initialize', () => {
|
|
27
|
-
it('should create directory if it does not exist', async () => {
|
|
28
|
-
const newDir = join(tempDir, 'subdir', 'new-storage');
|
|
29
|
-
const newStorage = new MarkdownStorage({ storagePath: newDir });
|
|
30
|
-
await newStorage.initialize();
|
|
31
|
-
// Directory should be created
|
|
32
|
-
const { existsSync } = await import('fs');
|
|
33
|
-
expect(existsSync(newDir)).toBe(true);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('write edge cases', () => {
|
|
38
|
-
it('should handle memory with empty title', async () => {
|
|
39
|
-
const memory = {
|
|
40
|
-
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
41
|
-
createdAt: '2024-03-15T10:30:00Z',
|
|
42
|
-
updatedAt: '2024-03-15T10:30:00Z',
|
|
43
|
-
tags: [],
|
|
44
|
-
category: 'test',
|
|
45
|
-
importance: 3,
|
|
46
|
-
title: '',
|
|
47
|
-
content: 'Content with empty title',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const filePath = await storage.write(memory);
|
|
51
|
-
expect(filePath).toContain('untitled');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should handle memory with special characters in title', async () => {
|
|
55
|
-
const memory = {
|
|
56
|
-
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
57
|
-
createdAt: '2024-03-15T10:30:00Z',
|
|
58
|
-
updatedAt: '2024-03-15T10:30:00Z',
|
|
59
|
-
tags: [],
|
|
60
|
-
category: 'test',
|
|
61
|
-
importance: 3,
|
|
62
|
-
title: 'Title with @#$%^&*()',
|
|
63
|
-
content: 'Content',
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const filePath = await storage.write(memory);
|
|
67
|
-
expect(filePath).toBeDefined();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should handle very long content', async () => {
|
|
71
|
-
const memory = {
|
|
72
|
-
id: '550e8400-e29b-41d4-a716-446655440002',
|
|
73
|
-
createdAt: '2024-03-15T10:30:00Z',
|
|
74
|
-
updatedAt: '2024-03-15T10:30:00Z',
|
|
75
|
-
tags: [],
|
|
76
|
-
category: 'test',
|
|
77
|
-
importance: 3,
|
|
78
|
-
title: 'Long Content',
|
|
79
|
-
content: 'A'.repeat(10000),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const filePath = await storage.write(memory);
|
|
83
|
-
const { readFileSync } = await import('fs');
|
|
84
|
-
const content = readFileSync(filePath, 'utf-8');
|
|
85
|
-
expect(content).toContain('A'.repeat(100));
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
describe('read edge cases', () => {
|
|
90
|
-
it('should throw error for invalid front matter', async () => {
|
|
91
|
-
const invalidContent = '---\ninvalid yaml: [\n---\n\n# Title';
|
|
92
|
-
writeFileSync(join(tempDir, 'invalid.md'), invalidContent);
|
|
93
|
-
|
|
94
|
-
await expect(storage.read('any-id')).rejects.toThrow(StorageError);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should throw error for missing required fields', async () => {
|
|
98
|
-
const incompleteContent = '---\nid: "test-id"\n---\n\n# Title';
|
|
99
|
-
writeFileSync(join(tempDir, 'incomplete.md'), incompleteContent);
|
|
100
|
-
|
|
101
|
-
await expect(storage.read('test-id')).rejects.toThrow(StorageError);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should handle file with no H1 title', async () => {
|
|
105
|
-
const noTitleContent = `---
|
|
106
|
-
id: "550e8400-e29b-41d4-a716-446655440000"
|
|
107
|
-
created_at: "2024-03-15T10:30:00Z"
|
|
108
|
-
updated_at: "2024-03-15T10:30:00Z"
|
|
109
|
-
tags: []
|
|
110
|
-
category: "general"
|
|
111
|
-
importance: 3
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
Just some content without H1.
|
|
115
|
-
`;
|
|
116
|
-
writeFileSync(join(tempDir, 'no-title.md'), noTitleContent);
|
|
117
|
-
|
|
118
|
-
const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
|
|
119
|
-
expect(memory.title).toBe('');
|
|
120
|
-
expect(memory.content).toContain('Just some content');
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe('list edge cases', () => {
|
|
125
|
-
it('should skip invalid markdown files', async () => {
|
|
126
|
-
// Create valid file
|
|
127
|
-
const validContent = `---
|
|
128
|
-
id: "valid-id"
|
|
129
|
-
created_at: "2024-03-15T10:30:00Z"
|
|
130
|
-
updated_at: "2024-03-15T10:30:00Z"
|
|
131
|
-
tags: []
|
|
132
|
-
category: "general"
|
|
133
|
-
importance: 3
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
# Valid
|
|
137
|
-
`;
|
|
138
|
-
writeFileSync(join(tempDir, 'valid.md'), validContent);
|
|
139
|
-
|
|
140
|
-
// Create invalid file
|
|
141
|
-
writeFileSync(join(tempDir, 'invalid.md'), 'Not valid markdown');
|
|
142
|
-
|
|
143
|
-
const files = await storage.list();
|
|
144
|
-
expect(files).toHaveLength(2);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should handle empty directory', async () => {
|
|
148
|
-
const files = await storage.list();
|
|
149
|
-
expect(files).toEqual([]);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should ignore non-markdown files', async () => {
|
|
153
|
-
writeFileSync(join(tempDir, 'test.txt'), 'Text file');
|
|
154
|
-
writeFileSync(join(tempDir, 'test.json'), '{}');
|
|
155
|
-
|
|
156
|
-
const files = await storage.list();
|
|
157
|
-
expect(files).toHaveLength(0);
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('findById edge cases', () => {
|
|
162
|
-
it('should return null when no files exist', async () => {
|
|
163
|
-
const result = await storage.findById('any-id');
|
|
164
|
-
expect(result).toBeNull();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should skip files with invalid front matter', async () => {
|
|
168
|
-
writeFileSync(join(tempDir, 'invalid.md'), 'Invalid content');
|
|
169
|
-
|
|
170
|
-
const result = await storage.findById('any-id');
|
|
171
|
-
expect(result).toBeNull();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('should find correct file among multiple files', async () => {
|
|
175
|
-
const content1 = `---
|
|
176
|
-
id: "id-1"
|
|
177
|
-
created_at: "2024-03-15T10:30:00Z"
|
|
178
|
-
updated_at: "2024-03-15T10:30:00Z"
|
|
179
|
-
tags: []
|
|
180
|
-
category: "general"
|
|
181
|
-
importance: 3
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
# File 1
|
|
185
|
-
`;
|
|
186
|
-
const content2 = `---
|
|
187
|
-
id: "id-2"
|
|
188
|
-
created_at: "2024-03-15T10:30:00Z"
|
|
189
|
-
updated_at: "2024-03-15T10:30:00Z"
|
|
190
|
-
tags: []
|
|
191
|
-
category: "general"
|
|
192
|
-
importance: 3
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
# File 2
|
|
196
|
-
`;
|
|
197
|
-
writeFileSync(join(tempDir, 'file1.md'), content1);
|
|
198
|
-
writeFileSync(join(tempDir, 'file2.md'), content2);
|
|
199
|
-
|
|
200
|
-
const result = await storage.findById('id-2');
|
|
201
|
-
expect(result).toContain('file2.md');
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe('delete edge cases', () => {
|
|
206
|
-
it('should throw error when trying to delete non-existent memory', async () => {
|
|
207
|
-
await expect(storage.delete('non-existent-id')).rejects.toThrow(StorageError);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe('FrontMatterError', () => {
|
|
213
|
-
it('should create error with message', () => {
|
|
214
|
-
const error = new FrontMatterError('Test error');
|
|
215
|
-
expect(error.message).toBe('Test error');
|
|
216
|
-
expect(error.name).toBe('FrontMatterError');
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it('should create error with cause', () => {
|
|
220
|
-
const cause = new Error('Original error');
|
|
221
|
-
const error = new FrontMatterError('Test error', cause);
|
|
222
|
-
expect(error.cause).toBe(cause);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
describe('StorageError', () => {
|
|
227
|
-
it('should create error with message', () => {
|
|
228
|
-
const error = new StorageError('Test error');
|
|
229
|
-
expect(error.message).toBe('Test error');
|
|
230
|
-
expect(error.name).toBe('StorageError');
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('should create error with cause', () => {
|
|
234
|
-
const cause = new Error('Original error');
|
|
235
|
-
const error = new StorageError('Test error', cause);
|
|
236
|
-
expect(error.cause).toBe(cause);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Storage Edge Case Tests
|
|
3
|
+
* Additional tests for better coverage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
7
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
8
|
+
import { tmpdir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { MarkdownStorage, StorageError } from '../../src/storage/markdown-storage.js';
|
|
11
|
+
import { FrontMatterError } from '../../src/storage/frontmatter-parser.js';
|
|
12
|
+
|
|
13
|
+
describe('MarkdownStorage Edge Cases', () => {
|
|
14
|
+
let tempDir: string;
|
|
15
|
+
let storage: MarkdownStorage;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tempDir = mkdtempSync(join(tmpdir(), 'memhub-storage-edge-'));
|
|
19
|
+
storage = new MarkdownStorage({ storagePath: tempDir });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('initialize', () => {
|
|
27
|
+
it('should create directory if it does not exist', async () => {
|
|
28
|
+
const newDir = join(tempDir, 'subdir', 'new-storage');
|
|
29
|
+
const newStorage = new MarkdownStorage({ storagePath: newDir });
|
|
30
|
+
await newStorage.initialize();
|
|
31
|
+
// Directory should be created
|
|
32
|
+
const { existsSync } = await import('fs');
|
|
33
|
+
expect(existsSync(newDir)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('write edge cases', () => {
|
|
38
|
+
it('should handle memory with empty title', async () => {
|
|
39
|
+
const memory = {
|
|
40
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
41
|
+
createdAt: '2024-03-15T10:30:00Z',
|
|
42
|
+
updatedAt: '2024-03-15T10:30:00Z',
|
|
43
|
+
tags: [],
|
|
44
|
+
category: 'test',
|
|
45
|
+
importance: 3,
|
|
46
|
+
title: '',
|
|
47
|
+
content: 'Content with empty title',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const filePath = await storage.write(memory);
|
|
51
|
+
expect(filePath).toContain('untitled');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle memory with special characters in title', async () => {
|
|
55
|
+
const memory = {
|
|
56
|
+
id: '550e8400-e29b-41d4-a716-446655440001',
|
|
57
|
+
createdAt: '2024-03-15T10:30:00Z',
|
|
58
|
+
updatedAt: '2024-03-15T10:30:00Z',
|
|
59
|
+
tags: [],
|
|
60
|
+
category: 'test',
|
|
61
|
+
importance: 3,
|
|
62
|
+
title: 'Title with @#$%^&*()',
|
|
63
|
+
content: 'Content',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const filePath = await storage.write(memory);
|
|
67
|
+
expect(filePath).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle very long content', async () => {
|
|
71
|
+
const memory = {
|
|
72
|
+
id: '550e8400-e29b-41d4-a716-446655440002',
|
|
73
|
+
createdAt: '2024-03-15T10:30:00Z',
|
|
74
|
+
updatedAt: '2024-03-15T10:30:00Z',
|
|
75
|
+
tags: [],
|
|
76
|
+
category: 'test',
|
|
77
|
+
importance: 3,
|
|
78
|
+
title: 'Long Content',
|
|
79
|
+
content: 'A'.repeat(10000),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const filePath = await storage.write(memory);
|
|
83
|
+
const { readFileSync } = await import('fs');
|
|
84
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
85
|
+
expect(content).toContain('A'.repeat(100));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('read edge cases', () => {
|
|
90
|
+
it('should throw error for invalid front matter', async () => {
|
|
91
|
+
const invalidContent = '---\ninvalid yaml: [\n---\n\n# Title';
|
|
92
|
+
writeFileSync(join(tempDir, 'invalid.md'), invalidContent);
|
|
93
|
+
|
|
94
|
+
await expect(storage.read('any-id')).rejects.toThrow(StorageError);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should throw error for missing required fields', async () => {
|
|
98
|
+
const incompleteContent = '---\nid: "test-id"\n---\n\n# Title';
|
|
99
|
+
writeFileSync(join(tempDir, 'incomplete.md'), incompleteContent);
|
|
100
|
+
|
|
101
|
+
await expect(storage.read('test-id')).rejects.toThrow(StorageError);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should handle file with no H1 title', async () => {
|
|
105
|
+
const noTitleContent = `---
|
|
106
|
+
id: "550e8400-e29b-41d4-a716-446655440000"
|
|
107
|
+
created_at: "2024-03-15T10:30:00Z"
|
|
108
|
+
updated_at: "2024-03-15T10:30:00Z"
|
|
109
|
+
tags: []
|
|
110
|
+
category: "general"
|
|
111
|
+
importance: 3
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
Just some content without H1.
|
|
115
|
+
`;
|
|
116
|
+
writeFileSync(join(tempDir, 'no-title.md'), noTitleContent);
|
|
117
|
+
|
|
118
|
+
const memory = await storage.read('550e8400-e29b-41d4-a716-446655440000');
|
|
119
|
+
expect(memory.title).toBe('');
|
|
120
|
+
expect(memory.content).toContain('Just some content');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('list edge cases', () => {
|
|
125
|
+
it('should skip invalid markdown files', async () => {
|
|
126
|
+
// Create valid file
|
|
127
|
+
const validContent = `---
|
|
128
|
+
id: "valid-id"
|
|
129
|
+
created_at: "2024-03-15T10:30:00Z"
|
|
130
|
+
updated_at: "2024-03-15T10:30:00Z"
|
|
131
|
+
tags: []
|
|
132
|
+
category: "general"
|
|
133
|
+
importance: 3
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
# Valid
|
|
137
|
+
`;
|
|
138
|
+
writeFileSync(join(tempDir, 'valid.md'), validContent);
|
|
139
|
+
|
|
140
|
+
// Create invalid file
|
|
141
|
+
writeFileSync(join(tempDir, 'invalid.md'), 'Not valid markdown');
|
|
142
|
+
|
|
143
|
+
const files = await storage.list();
|
|
144
|
+
expect(files).toHaveLength(2);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle empty directory', async () => {
|
|
148
|
+
const files = await storage.list();
|
|
149
|
+
expect(files).toEqual([]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should ignore non-markdown files', async () => {
|
|
153
|
+
writeFileSync(join(tempDir, 'test.txt'), 'Text file');
|
|
154
|
+
writeFileSync(join(tempDir, 'test.json'), '{}');
|
|
155
|
+
|
|
156
|
+
const files = await storage.list();
|
|
157
|
+
expect(files).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('findById edge cases', () => {
|
|
162
|
+
it('should return null when no files exist', async () => {
|
|
163
|
+
const result = await storage.findById('any-id');
|
|
164
|
+
expect(result).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should skip files with invalid front matter', async () => {
|
|
168
|
+
writeFileSync(join(tempDir, 'invalid.md'), 'Invalid content');
|
|
169
|
+
|
|
170
|
+
const result = await storage.findById('any-id');
|
|
171
|
+
expect(result).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should find correct file among multiple files', async () => {
|
|
175
|
+
const content1 = `---
|
|
176
|
+
id: "id-1"
|
|
177
|
+
created_at: "2024-03-15T10:30:00Z"
|
|
178
|
+
updated_at: "2024-03-15T10:30:00Z"
|
|
179
|
+
tags: []
|
|
180
|
+
category: "general"
|
|
181
|
+
importance: 3
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
# File 1
|
|
185
|
+
`;
|
|
186
|
+
const content2 = `---
|
|
187
|
+
id: "id-2"
|
|
188
|
+
created_at: "2024-03-15T10:30:00Z"
|
|
189
|
+
updated_at: "2024-03-15T10:30:00Z"
|
|
190
|
+
tags: []
|
|
191
|
+
category: "general"
|
|
192
|
+
importance: 3
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# File 2
|
|
196
|
+
`;
|
|
197
|
+
writeFileSync(join(tempDir, 'file1.md'), content1);
|
|
198
|
+
writeFileSync(join(tempDir, 'file2.md'), content2);
|
|
199
|
+
|
|
200
|
+
const result = await storage.findById('id-2');
|
|
201
|
+
expect(result).toContain('file2.md');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('delete edge cases', () => {
|
|
206
|
+
it('should throw error when trying to delete non-existent memory', async () => {
|
|
207
|
+
await expect(storage.delete('non-existent-id')).rejects.toThrow(StorageError);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('FrontMatterError', () => {
|
|
213
|
+
it('should create error with message', () => {
|
|
214
|
+
const error = new FrontMatterError('Test error');
|
|
215
|
+
expect(error.message).toBe('Test error');
|
|
216
|
+
expect(error.name).toBe('FrontMatterError');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should create error with cause', () => {
|
|
220
|
+
const cause = new Error('Original error');
|
|
221
|
+
const error = new FrontMatterError('Test error', cause);
|
|
222
|
+
expect(error.cause).toBe(cause);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('StorageError', () => {
|
|
227
|
+
it('should create error with message', () => {
|
|
228
|
+
const error = new StorageError('Test error');
|
|
229
|
+
expect(error.message).toBe('Test error');
|
|
230
|
+
expect(error.name).toBe('StorageError');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should create error with cause', () => {
|
|
234
|
+
const cause = new Error('Original error');
|
|
235
|
+
const error = new StorageError('Test error', cause);
|
|
236
|
+
expect(error.cause).toBe(cause);
|
|
237
|
+
});
|
|
238
|
+
});
|