kaimon-cli 0.2.1 → 0.3.0
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/README.md +37 -3
- package/dist/__tests__/commands/list.test.d.ts +2 -0
- package/dist/__tests__/commands/list.test.d.ts.map +1 -0
- package/dist/__tests__/commands/list.test.js +109 -0
- package/dist/__tests__/commands/list.test.js.map +1 -0
- package/dist/__tests__/commands/pull.test.js +152 -57
- package/dist/__tests__/commands/pull.test.js.map +1 -1
- package/dist/__tests__/integration/complete-user-journeys.test.d.ts +2 -0
- package/dist/__tests__/integration/complete-user-journeys.test.d.ts.map +1 -0
- package/dist/__tests__/integration/complete-user-journeys.test.js +348 -0
- package/dist/__tests__/integration/complete-user-journeys.test.js.map +1 -0
- package/dist/__tests__/integration/config-integration.test.d.ts +2 -0
- package/dist/__tests__/integration/config-integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/config-integration.test.js +151 -0
- package/dist/__tests__/integration/config-integration.test.js.map +1 -0
- package/dist/__tests__/integration/data-integrity.test.d.ts +2 -0
- package/dist/__tests__/integration/data-integrity.test.d.ts.map +1 -0
- package/dist/__tests__/integration/data-integrity.test.js +296 -0
- package/dist/__tests__/integration/data-integrity.test.js.map +1 -0
- package/dist/__tests__/integration/edge-cases-advanced.test.d.ts +2 -0
- package/dist/__tests__/integration/edge-cases-advanced.test.d.ts.map +1 -0
- package/dist/__tests__/integration/edge-cases-advanced.test.js +286 -0
- package/dist/__tests__/integration/edge-cases-advanced.test.js.map +1 -0
- package/dist/__tests__/integration/error-recovery.test.d.ts +2 -0
- package/dist/__tests__/integration/error-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/integration/error-recovery.test.js +277 -0
- package/dist/__tests__/integration/error-recovery.test.js.map +1 -0
- package/dist/__tests__/integration/filesystem-edge-cases.test.d.ts +2 -0
- package/dist/__tests__/integration/filesystem-edge-cases.test.d.ts.map +1 -0
- package/dist/__tests__/integration/filesystem-edge-cases.test.js +218 -0
- package/dist/__tests__/integration/filesystem-edge-cases.test.js.map +1 -0
- package/dist/__tests__/integration/filtering-operations.test.d.ts +2 -0
- package/dist/__tests__/integration/filtering-operations.test.d.ts.map +1 -0
- package/dist/__tests__/integration/filtering-operations.test.js +278 -0
- package/dist/__tests__/integration/filtering-operations.test.js.map +1 -0
- package/dist/__tests__/integration/state-transitions.test.d.ts +2 -0
- package/dist/__tests__/integration/state-transitions.test.d.ts.map +1 -0
- package/dist/__tests__/integration/state-transitions.test.js +320 -0
- package/dist/__tests__/integration/state-transitions.test.js.map +1 -0
- package/dist/commands/list.d.ts +4 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +19 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +5 -3
- package/dist/commands/pull.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/listOperations.d.ts +5 -0
- package/dist/utils/listOperations.d.ts.map +1 -0
- package/dist/utils/listOperations.js +138 -0
- package/dist/utils/listOperations.js.map +1 -0
- package/dist/utils/pullOperations.d.ts +3 -2
- package/dist/utils/pullOperations.d.ts.map +1 -1
- package/dist/utils/pullOperations.js +52 -8
- package/dist/utils/pullOperations.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { pullDocuments } from '../../utils/pullOperations.js';
|
|
6
|
+
import * as config from '../../utils/config.js';
|
|
7
|
+
import * as supabaseClient from '../../utils/supabaseClient.js';
|
|
8
|
+
import * as tokenRefresh from '../../utils/tokenRefresh.js';
|
|
9
|
+
import { createTempDir, cleanupTempDir, mockConfig, mockDocumentRecord, mockBlockNoteContent, createMockSupabaseClient } from '../helpers.js';
|
|
10
|
+
describe('Filtering Operations (Integration)', () => {
|
|
11
|
+
let tempDir;
|
|
12
|
+
let docsDir;
|
|
13
|
+
let consoleSpy;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tempDir = createTempDir();
|
|
16
|
+
docsDir = path.join(tempDir, 'KaimonDocs');
|
|
17
|
+
vi.spyOn(os, 'homedir').mockReturnValue(tempDir);
|
|
18
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
19
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
cleanupTempDir(tempDir);
|
|
23
|
+
vi.restoreAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it('should pull 5 specific docs by mix of titles and IDs from pool of 20', async () => {
|
|
26
|
+
config.saveConfig(mockConfig());
|
|
27
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
28
|
+
const mockSupabase = createMockSupabaseClient();
|
|
29
|
+
// Create 20 documents
|
|
30
|
+
const mockDocs = Array.from({ length: 20 }, (_, i) => mockDocumentRecord({
|
|
31
|
+
id: `doc-${String(i + 1).padStart(3, '0')}`,
|
|
32
|
+
title: `Document ${String.fromCharCode(65 + i)}`, // A-T
|
|
33
|
+
updated_at: new Date().toISOString()
|
|
34
|
+
}));
|
|
35
|
+
mockSupabase.from.mockReturnValue({
|
|
36
|
+
select: vi.fn().mockReturnThis(),
|
|
37
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
38
|
+
});
|
|
39
|
+
const mockContent = mockBlockNoteContent();
|
|
40
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
41
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
42
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
43
|
+
});
|
|
44
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
45
|
+
// Pull: 3 by title + 2 by ID
|
|
46
|
+
// Titles: "Document A", "Document E", "Document J"
|
|
47
|
+
// IDs: "doc-015" (Document O), "doc-020" (Document T)
|
|
48
|
+
await pullDocuments(false, true, ['Document A', 'Document E', 'Document J', 'doc-015', 'doc-020']);
|
|
49
|
+
// Verify exactly 5 files created
|
|
50
|
+
const files = fs.readdirSync(docsDir);
|
|
51
|
+
expect(files.length).toBe(5);
|
|
52
|
+
const filenames = files.map(f => f.replace('.md', ''));
|
|
53
|
+
expect(filenames).toContain('Document-A');
|
|
54
|
+
expect(filenames).toContain('Document-E');
|
|
55
|
+
expect(filenames).toContain('Document-J');
|
|
56
|
+
expect(filenames).toContain('Document-O'); // doc-015
|
|
57
|
+
expect(filenames).toContain('Document-T'); // doc-020
|
|
58
|
+
// Should NOT contain others
|
|
59
|
+
expect(filenames).not.toContain('Document-B');
|
|
60
|
+
expect(filenames).not.toContain('Document-C');
|
|
61
|
+
expect(filenames).not.toContain('Document-Z');
|
|
62
|
+
});
|
|
63
|
+
it('should handle pull with mix of existing and nonexistent documents', async () => {
|
|
64
|
+
config.saveConfig(mockConfig());
|
|
65
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
66
|
+
const mockSupabase = createMockSupabaseClient();
|
|
67
|
+
const mockDocs = [
|
|
68
|
+
mockDocumentRecord({ id: 'doc-1', title: 'Document A' }),
|
|
69
|
+
mockDocumentRecord({ id: 'doc-2', title: 'Document B' }),
|
|
70
|
+
mockDocumentRecord({ id: 'doc-3', title: 'Document C' })
|
|
71
|
+
];
|
|
72
|
+
mockSupabase.from.mockReturnValue({
|
|
73
|
+
select: vi.fn().mockReturnThis(),
|
|
74
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
75
|
+
});
|
|
76
|
+
const mockContent = mockBlockNoteContent();
|
|
77
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
78
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
79
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
80
|
+
});
|
|
81
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
82
|
+
// Pull: 2 real + 2 fake
|
|
83
|
+
await pullDocuments(false, true, ['Document A', 'Fake Document', 'Document C', 'nonexistent-id']);
|
|
84
|
+
// Verify only 2 files created (the real ones)
|
|
85
|
+
const files = fs.readdirSync(docsDir);
|
|
86
|
+
expect(files.length).toBe(2);
|
|
87
|
+
const filenames = files.map(f => f.replace('.md', ''));
|
|
88
|
+
expect(filenames).toContain('Document-A');
|
|
89
|
+
expect(filenames).toContain('Document-C');
|
|
90
|
+
// Should show warnings for nonexistent
|
|
91
|
+
const output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
92
|
+
expect(output).toMatch(/not found|not available/i);
|
|
93
|
+
expect(output).toContain('Fake Document');
|
|
94
|
+
expect(output).toContain('nonexistent-id');
|
|
95
|
+
});
|
|
96
|
+
it('should handle filtering when only nonexistent documents requested', async () => {
|
|
97
|
+
config.saveConfig(mockConfig());
|
|
98
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
99
|
+
const mockSupabase = createMockSupabaseClient();
|
|
100
|
+
const mockDocs = [
|
|
101
|
+
mockDocumentRecord({ id: 'doc-1', title: 'Real Document' })
|
|
102
|
+
];
|
|
103
|
+
mockSupabase.from.mockReturnValue({
|
|
104
|
+
select: vi.fn().mockReturnThis(),
|
|
105
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
106
|
+
});
|
|
107
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
108
|
+
// Pull: All nonexistent
|
|
109
|
+
await pullDocuments(false, true, ['Fake 1', 'Fake 2', 'fake-id-123']);
|
|
110
|
+
// Should create no files
|
|
111
|
+
expect(fs.existsSync(docsDir)).toBe(false);
|
|
112
|
+
// Should show error message
|
|
113
|
+
const output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
114
|
+
expect(output).toMatch(/No matching documents|not found/i);
|
|
115
|
+
});
|
|
116
|
+
it('should handle duplicate selections (same doc requested multiple times)', async () => {
|
|
117
|
+
config.saveConfig(mockConfig());
|
|
118
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
119
|
+
const mockSupabase = createMockSupabaseClient();
|
|
120
|
+
const mockDocs = [
|
|
121
|
+
mockDocumentRecord({ id: 'doc-123', title: 'Duplicate Test' })
|
|
122
|
+
];
|
|
123
|
+
mockSupabase.from.mockReturnValue({
|
|
124
|
+
select: vi.fn().mockReturnThis(),
|
|
125
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
126
|
+
});
|
|
127
|
+
const mockContent = mockBlockNoteContent();
|
|
128
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
129
|
+
const downloadSpy = vi.fn().mockResolvedValue({ data: blob, error: null });
|
|
130
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
131
|
+
download: downloadSpy
|
|
132
|
+
});
|
|
133
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
134
|
+
// Request same document 3 times (by title and ID mixed)
|
|
135
|
+
// "Duplicate Test" and "doc-123" refer to same document
|
|
136
|
+
await pullDocuments(false, true, ['Duplicate Test', 'doc-123', 'Duplicate Test']);
|
|
137
|
+
// Should create only 1 file (deduplication works)
|
|
138
|
+
const files = fs.readdirSync(docsDir);
|
|
139
|
+
expect(files.length).toBe(1);
|
|
140
|
+
// Should download only once (deduplicated by document ID)
|
|
141
|
+
expect(downloadSpy).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
it('should handle filtering with special characters in titles', async () => {
|
|
144
|
+
config.saveConfig(mockConfig());
|
|
145
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
146
|
+
const mockSupabase = createMockSupabaseClient();
|
|
147
|
+
const mockDocs = [
|
|
148
|
+
mockDocumentRecord({ id: 'doc-1', title: 'Document: "Special" <Chars>' }),
|
|
149
|
+
mockDocumentRecord({ id: 'doc-2', title: 'Regular Document' }),
|
|
150
|
+
mockDocumentRecord({ id: 'doc-3', title: 'Document & More!' })
|
|
151
|
+
];
|
|
152
|
+
mockSupabase.from.mockReturnValue({
|
|
153
|
+
select: vi.fn().mockReturnThis(),
|
|
154
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
155
|
+
});
|
|
156
|
+
const mockContent = mockBlockNoteContent();
|
|
157
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
158
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
159
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
160
|
+
});
|
|
161
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
162
|
+
// Pull by exact title match (with special chars)
|
|
163
|
+
await pullDocuments(false, true, ['Document: "Special" <Chars>', 'Document & More!']);
|
|
164
|
+
// Should create 2 files with sanitized names
|
|
165
|
+
const files = fs.readdirSync(docsDir);
|
|
166
|
+
expect(files.length).toBe(2);
|
|
167
|
+
// Filenames should have special chars sanitized
|
|
168
|
+
const filenames = files.map(f => f.replace('.md', ''));
|
|
169
|
+
// Check for sanitized versions (special chars replaced with -)
|
|
170
|
+
expect(filenames.some(f => f.includes('Special') && f.includes('Chars'))).toBe(true);
|
|
171
|
+
expect(filenames.some(f => f.includes('More'))).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it('should handle case-sensitive filtering (exact match required)', async () => {
|
|
174
|
+
config.saveConfig(mockConfig());
|
|
175
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
176
|
+
const mockSupabase = createMockSupabaseClient();
|
|
177
|
+
const mockDocs = [
|
|
178
|
+
mockDocumentRecord({ id: 'doc-1', title: 'Document A' }),
|
|
179
|
+
mockDocumentRecord({ id: 'doc-2', title: 'document a' }), // lowercase
|
|
180
|
+
mockDocumentRecord({ id: 'doc-3', title: 'DOCUMENT A' }) // uppercase
|
|
181
|
+
];
|
|
182
|
+
mockSupabase.from.mockReturnValue({
|
|
183
|
+
select: vi.fn().mockReturnThis(),
|
|
184
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
185
|
+
});
|
|
186
|
+
const mockContent = mockBlockNoteContent();
|
|
187
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
188
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
189
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
190
|
+
});
|
|
191
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
192
|
+
// Pull: Case-sensitive match
|
|
193
|
+
await pullDocuments(false, true, ['Document A']);
|
|
194
|
+
// Should create only 1 file (exact match)
|
|
195
|
+
const files = fs.readdirSync(docsDir);
|
|
196
|
+
expect(files.length).toBe(1);
|
|
197
|
+
const content = fs.readFileSync(path.join(docsDir, files[0]), 'utf-8');
|
|
198
|
+
expect(content).toContain('kaimon_id: "doc-1"');
|
|
199
|
+
});
|
|
200
|
+
it('should handle filtering with very long selection lists (50+ items)', async () => {
|
|
201
|
+
config.saveConfig(mockConfig());
|
|
202
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
203
|
+
const mockSupabase = createMockSupabaseClient();
|
|
204
|
+
// Create 100 documents
|
|
205
|
+
const mockDocs = Array.from({ length: 100 }, (_, i) => mockDocumentRecord({
|
|
206
|
+
id: `doc-${String(i + 1).padStart(3, '0')}`,
|
|
207
|
+
title: `Document ${i + 1}`,
|
|
208
|
+
updated_at: new Date().toISOString()
|
|
209
|
+
}));
|
|
210
|
+
mockSupabase.from.mockReturnValue({
|
|
211
|
+
select: vi.fn().mockReturnThis(),
|
|
212
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
213
|
+
});
|
|
214
|
+
const mockContent = mockBlockNoteContent();
|
|
215
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
216
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
217
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
218
|
+
});
|
|
219
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
220
|
+
// Request 50 documents by ID
|
|
221
|
+
const selections = Array.from({ length: 50 }, (_, i) => `doc-${String(i + 1).padStart(3, '0')}`);
|
|
222
|
+
await pullDocuments(false, true, selections);
|
|
223
|
+
// Verify exactly 50 files created
|
|
224
|
+
const files = fs.readdirSync(docsDir);
|
|
225
|
+
expect(files.length).toBe(50);
|
|
226
|
+
});
|
|
227
|
+
it('should filter correctly when some selected docs are already up-to-date', async () => {
|
|
228
|
+
config.saveConfig(mockConfig());
|
|
229
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
230
|
+
const mockSupabase = createMockSupabaseClient();
|
|
231
|
+
// Create 2 existing local files (up-to-date)
|
|
232
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
233
|
+
const upToDateTime = new Date('2024-01-01T00:00:00Z');
|
|
234
|
+
fs.writeFileSync(path.join(docsDir, 'Document-A.md'), `---
|
|
235
|
+
kaimon_id: "doc-1"
|
|
236
|
+
title: "Document A"
|
|
237
|
+
---
|
|
238
|
+
Existing content A`, 'utf-8');
|
|
239
|
+
fs.utimesSync(path.join(docsDir, 'Document-A.md'), upToDateTime, upToDateTime);
|
|
240
|
+
fs.writeFileSync(path.join(docsDir, 'Document-B.md'), `---
|
|
241
|
+
kaimon_id: "doc-2"
|
|
242
|
+
title: "Document B"
|
|
243
|
+
---
|
|
244
|
+
Existing content B`, 'utf-8');
|
|
245
|
+
fs.utimesSync(path.join(docsDir, 'Document-B.md'), upToDateTime, upToDateTime);
|
|
246
|
+
// Remote has 4 documents (2 up-to-date, 2 new)
|
|
247
|
+
const mockDocs = [
|
|
248
|
+
mockDocumentRecord({ id: 'doc-1', title: 'Document A', updated_at: upToDateTime.toISOString() }), // Up-to-date
|
|
249
|
+
mockDocumentRecord({ id: 'doc-2', title: 'Document B', updated_at: upToDateTime.toISOString() }), // Up-to-date
|
|
250
|
+
mockDocumentRecord({ id: 'doc-3', title: 'Document C', updated_at: new Date().toISOString() }), // New
|
|
251
|
+
mockDocumentRecord({ id: 'doc-4', title: 'Document D', updated_at: new Date().toISOString() }) // New
|
|
252
|
+
];
|
|
253
|
+
mockSupabase.from.mockReturnValue({
|
|
254
|
+
select: vi.fn().mockReturnThis(),
|
|
255
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
256
|
+
});
|
|
257
|
+
const mockContent = mockBlockNoteContent();
|
|
258
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
259
|
+
const downloadSpy = vi.fn().mockResolvedValue({ data: blob, error: null });
|
|
260
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
261
|
+
download: downloadSpy
|
|
262
|
+
});
|
|
263
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
264
|
+
// Request all 4 documents
|
|
265
|
+
await pullDocuments(false, true, ['Document A', 'Document B', 'Document C', 'Document D']);
|
|
266
|
+
// Should have 4 files total (2 existing + 2 new)
|
|
267
|
+
const files = fs.readdirSync(docsDir);
|
|
268
|
+
expect(files.length).toBe(4);
|
|
269
|
+
// Should only download 2 new ones (not the up-to-date ones)
|
|
270
|
+
expect(downloadSpy).toHaveBeenCalledTimes(2);
|
|
271
|
+
// Verify existing files weren't modified
|
|
272
|
+
const contentA = fs.readFileSync(path.join(docsDir, 'Document-A.md'), 'utf-8');
|
|
273
|
+
const contentB = fs.readFileSync(path.join(docsDir, 'Document-B.md'), 'utf-8');
|
|
274
|
+
expect(contentA).toContain('Existing content A');
|
|
275
|
+
expect(contentB).toContain('Existing content B');
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
//# sourceMappingURL=filtering-operations.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filtering-operations.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/filtering-operations.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-transitions.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/integration/state-transitions.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { listDocuments } from '../../utils/listOperations.js';
|
|
6
|
+
import { pullDocuments } from '../../utils/pullOperations.js';
|
|
7
|
+
import * as config from '../../utils/config.js';
|
|
8
|
+
import * as supabaseClient from '../../utils/supabaseClient.js';
|
|
9
|
+
import * as tokenRefresh from '../../utils/tokenRefresh.js';
|
|
10
|
+
import { createTempDir, cleanupTempDir, mockConfig, mockDocumentRecord, mockBlockNoteContent, createMockSupabaseClient } from '../helpers.js';
|
|
11
|
+
describe('State Transitions (Integration)', () => {
|
|
12
|
+
let tempDir;
|
|
13
|
+
let docsDir;
|
|
14
|
+
let consoleSpy;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tempDir = createTempDir();
|
|
17
|
+
docsDir = path.join(tempDir, 'KaimonDocs');
|
|
18
|
+
vi.spyOn(os, 'homedir').mockReturnValue(tempDir);
|
|
19
|
+
vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
|
|
20
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
cleanupTempDir(tempDir);
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it('should transition NEW → UP-TO-DATE after pull', async () => {
|
|
27
|
+
config.saveConfig(mockConfig());
|
|
28
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
29
|
+
const mockSupabase = createMockSupabaseClient();
|
|
30
|
+
const doc = mockDocumentRecord({
|
|
31
|
+
id: 'doc-123',
|
|
32
|
+
title: 'New Document',
|
|
33
|
+
updated_at: new Date('2024-01-01T12:00:00Z').toISOString()
|
|
34
|
+
});
|
|
35
|
+
mockSupabase.from.mockReturnValue({
|
|
36
|
+
select: vi.fn().mockReturnThis(),
|
|
37
|
+
order: vi.fn().mockResolvedValue({ data: [doc], error: null })
|
|
38
|
+
});
|
|
39
|
+
const mockContent = mockBlockNoteContent();
|
|
40
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
41
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
42
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
43
|
+
});
|
|
44
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
45
|
+
// Before pull: Should show [NEW]
|
|
46
|
+
await listDocuments();
|
|
47
|
+
let output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
48
|
+
expect(output).toContain('[NEW]');
|
|
49
|
+
expect(output).not.toContain('[UP-TO-DATE]');
|
|
50
|
+
// Pull the document
|
|
51
|
+
await pullDocuments(false, true);
|
|
52
|
+
expect(fs.existsSync(docsDir)).toBe(true);
|
|
53
|
+
// After pull: Should show [UP-TO-DATE]
|
|
54
|
+
consoleSpy.mockClear();
|
|
55
|
+
await listDocuments();
|
|
56
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
57
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
58
|
+
expect(output).not.toContain('[NEW]');
|
|
59
|
+
});
|
|
60
|
+
it('should transition UP-TO-DATE → UPDATED when remote changes', async () => {
|
|
61
|
+
config.saveConfig(mockConfig());
|
|
62
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
63
|
+
const mockSupabase = createMockSupabaseClient();
|
|
64
|
+
// Initial state
|
|
65
|
+
const initialTime = new Date('2024-01-01T12:00:00Z');
|
|
66
|
+
const doc = mockDocumentRecord({
|
|
67
|
+
id: 'doc-123',
|
|
68
|
+
title: 'Changing Document',
|
|
69
|
+
updated_at: initialTime.toISOString()
|
|
70
|
+
});
|
|
71
|
+
mockSupabase.from.mockReturnValue({
|
|
72
|
+
select: vi.fn().mockReturnThis(),
|
|
73
|
+
order: vi.fn().mockResolvedValue({ data: [doc], error: null })
|
|
74
|
+
});
|
|
75
|
+
const mockContent = mockBlockNoteContent();
|
|
76
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
77
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
78
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
79
|
+
});
|
|
80
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
81
|
+
// Pull initial version
|
|
82
|
+
await pullDocuments(false, true);
|
|
83
|
+
// Verify UP-TO-DATE
|
|
84
|
+
consoleSpy.mockClear();
|
|
85
|
+
await listDocuments();
|
|
86
|
+
let output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
87
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
88
|
+
// Simulate remote update
|
|
89
|
+
const updatedTime = new Date('2024-01-02T12:00:00Z');
|
|
90
|
+
const updatedDoc = mockDocumentRecord({
|
|
91
|
+
id: 'doc-123',
|
|
92
|
+
title: 'Changing Document',
|
|
93
|
+
updated_at: updatedTime.toISOString()
|
|
94
|
+
});
|
|
95
|
+
mockSupabase.from.mockReturnValue({
|
|
96
|
+
select: vi.fn().mockReturnThis(),
|
|
97
|
+
order: vi.fn().mockResolvedValue({ data: [updatedDoc], error: null })
|
|
98
|
+
});
|
|
99
|
+
// List should now show [UPDATED]
|
|
100
|
+
consoleSpy.mockClear();
|
|
101
|
+
await listDocuments();
|
|
102
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
103
|
+
expect(output).toContain('[UPDATED]');
|
|
104
|
+
expect(output).not.toContain('[UP-TO-DATE]');
|
|
105
|
+
});
|
|
106
|
+
it('should transition UPDATED → UP-TO-DATE after pulling updates', async () => {
|
|
107
|
+
config.saveConfig(mockConfig());
|
|
108
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
109
|
+
const mockSupabase = createMockSupabaseClient();
|
|
110
|
+
// Create initial local file (older version)
|
|
111
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
112
|
+
const localTime = new Date('2024-01-01T00:00:00Z');
|
|
113
|
+
const localFile = path.join(docsDir, 'Updated-Doc.md');
|
|
114
|
+
fs.writeFileSync(localFile, `---
|
|
115
|
+
kaimon_id: "doc-123"
|
|
116
|
+
title: "Updated Doc"
|
|
117
|
+
---
|
|
118
|
+
Old content`, 'utf-8');
|
|
119
|
+
fs.utimesSync(localFile, localTime, localTime);
|
|
120
|
+
// Remote has newer version
|
|
121
|
+
const remoteTime = new Date('2024-01-02T00:00:00Z');
|
|
122
|
+
const doc = mockDocumentRecord({
|
|
123
|
+
id: 'doc-123',
|
|
124
|
+
title: 'Updated Doc',
|
|
125
|
+
updated_at: remoteTime.toISOString()
|
|
126
|
+
});
|
|
127
|
+
mockSupabase.from.mockReturnValue({
|
|
128
|
+
select: vi.fn().mockReturnThis(),
|
|
129
|
+
order: vi.fn().mockResolvedValue({ data: [doc], error: null })
|
|
130
|
+
});
|
|
131
|
+
const updatedContent = {
|
|
132
|
+
type: 'blocknote',
|
|
133
|
+
content: [
|
|
134
|
+
{ type: 'heading', props: { level: 1 }, content: [{ type: 'text', text: 'New Content' }] }
|
|
135
|
+
]
|
|
136
|
+
};
|
|
137
|
+
const blob = new Blob([JSON.stringify(updatedContent)], { type: 'application/json' });
|
|
138
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
139
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
140
|
+
});
|
|
141
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
142
|
+
// Before pull: Should show [UPDATED]
|
|
143
|
+
await listDocuments();
|
|
144
|
+
let output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
145
|
+
expect(output).toContain('[UPDATED]');
|
|
146
|
+
// Pull the updates
|
|
147
|
+
await pullDocuments(false, true);
|
|
148
|
+
// After pull: Should show [UP-TO-DATE]
|
|
149
|
+
consoleSpy.mockClear();
|
|
150
|
+
await listDocuments();
|
|
151
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
152
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
153
|
+
expect(output).not.toContain('[UPDATED]');
|
|
154
|
+
// Verify file was actually updated
|
|
155
|
+
const fileContent = fs.readFileSync(localFile, 'utf-8');
|
|
156
|
+
expect(fileContent).toContain('# New Content');
|
|
157
|
+
});
|
|
158
|
+
it('should handle multiple documents with mixed statuses (2 NEW, 3 UPDATED, 5 UP-TO-DATE)', async () => {
|
|
159
|
+
config.saveConfig(mockConfig());
|
|
160
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
161
|
+
const mockSupabase = createMockSupabaseClient();
|
|
162
|
+
// Create 5 existing local files (will be UP-TO-DATE)
|
|
163
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
164
|
+
const upToDateTime = new Date('2024-01-01T00:00:00Z');
|
|
165
|
+
for (let i = 1; i <= 5; i++) {
|
|
166
|
+
const file = path.join(docsDir, `UpToDate-${i}.md`);
|
|
167
|
+
fs.writeFileSync(file, `---
|
|
168
|
+
kaimon_id: "up-to-date-${i}"
|
|
169
|
+
---
|
|
170
|
+
Content ${i}`, 'utf-8');
|
|
171
|
+
fs.utimesSync(file, upToDateTime, upToDateTime);
|
|
172
|
+
}
|
|
173
|
+
// Create 3 existing local files that will be outdated (UPDATED)
|
|
174
|
+
const oldTime = new Date('2024-01-01T00:00:00Z');
|
|
175
|
+
for (let i = 1; i <= 3; i++) {
|
|
176
|
+
const file = path.join(docsDir, `Updated-${i}.md`);
|
|
177
|
+
fs.writeFileSync(file, `---
|
|
178
|
+
kaimon_id: "updated-${i}"
|
|
179
|
+
---
|
|
180
|
+
Old content ${i}`, 'utf-8');
|
|
181
|
+
fs.utimesSync(file, oldTime, oldTime);
|
|
182
|
+
}
|
|
183
|
+
// Remote has all 10 documents
|
|
184
|
+
const mockDocs = [
|
|
185
|
+
// 2 NEW documents (not in local)
|
|
186
|
+
mockDocumentRecord({ id: 'new-1', title: 'New 1', updated_at: new Date().toISOString() }),
|
|
187
|
+
mockDocumentRecord({ id: 'new-2', title: 'New 2', updated_at: new Date().toISOString() }),
|
|
188
|
+
// 3 UPDATED documents (newer than local)
|
|
189
|
+
...Array.from({ length: 3 }, (_, i) => mockDocumentRecord({
|
|
190
|
+
id: `updated-${i + 1}`,
|
|
191
|
+
title: `Updated ${i + 1}`,
|
|
192
|
+
updated_at: new Date('2024-01-02T00:00:00Z').toISOString() // Newer than local
|
|
193
|
+
})),
|
|
194
|
+
// 5 UP-TO-DATE documents (same or older than local)
|
|
195
|
+
...Array.from({ length: 5 }, (_, i) => mockDocumentRecord({
|
|
196
|
+
id: `up-to-date-${i + 1}`,
|
|
197
|
+
title: `UpToDate ${i + 1}`,
|
|
198
|
+
updated_at: upToDateTime.toISOString()
|
|
199
|
+
}))
|
|
200
|
+
];
|
|
201
|
+
mockSupabase.from.mockReturnValue({
|
|
202
|
+
select: vi.fn().mockReturnThis(),
|
|
203
|
+
order: vi.fn().mockResolvedValue({ data: mockDocs, error: null })
|
|
204
|
+
});
|
|
205
|
+
const mockContent = mockBlockNoteContent();
|
|
206
|
+
const blob = new Blob([JSON.stringify(mockContent)], { type: 'application/json' });
|
|
207
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
208
|
+
download: vi.fn().mockResolvedValue({ data: blob, error: null })
|
|
209
|
+
});
|
|
210
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
211
|
+
// List should show mixed statuses
|
|
212
|
+
await listDocuments();
|
|
213
|
+
const output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
214
|
+
// Count status indicators
|
|
215
|
+
const newCount = (output.match(/\[NEW\]/g) || []).length;
|
|
216
|
+
const updatedCount = (output.match(/\[UPDATED\]/g) || []).length;
|
|
217
|
+
const upToDateCount = (output.match(/\[UP-TO-DATE\]/g) || []).length;
|
|
218
|
+
expect(newCount).toBe(2);
|
|
219
|
+
expect(updatedCount).toBe(3);
|
|
220
|
+
expect(upToDateCount).toBe(5);
|
|
221
|
+
expect(output).toContain('10 total');
|
|
222
|
+
});
|
|
223
|
+
it('should keep UP-TO-DATE status when local file is newer than remote', async () => {
|
|
224
|
+
config.saveConfig(mockConfig());
|
|
225
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
226
|
+
const mockSupabase = createMockSupabaseClient();
|
|
227
|
+
// Create local file with NEWER timestamp than remote
|
|
228
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
229
|
+
const localTime = new Date('2024-01-05T00:00:00Z'); // Newer
|
|
230
|
+
const localFile = path.join(docsDir, 'Local-Newer.md');
|
|
231
|
+
fs.writeFileSync(localFile, `---
|
|
232
|
+
kaimon_id: "doc-123"
|
|
233
|
+
title: "Local Newer"
|
|
234
|
+
---
|
|
235
|
+
Local edits`, 'utf-8');
|
|
236
|
+
fs.utimesSync(localFile, localTime, localTime);
|
|
237
|
+
// Remote has OLDER version
|
|
238
|
+
const remoteTime = new Date('2024-01-01T00:00:00Z'); // Older
|
|
239
|
+
const doc = mockDocumentRecord({
|
|
240
|
+
id: 'doc-123',
|
|
241
|
+
title: 'Local Newer',
|
|
242
|
+
updated_at: remoteTime.toISOString()
|
|
243
|
+
});
|
|
244
|
+
mockSupabase.from.mockReturnValue({
|
|
245
|
+
select: vi.fn().mockReturnThis(),
|
|
246
|
+
order: vi.fn().mockResolvedValue({ data: [doc], error: null })
|
|
247
|
+
});
|
|
248
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
249
|
+
// List should show [UP-TO-DATE] (not UPDATED)
|
|
250
|
+
await listDocuments();
|
|
251
|
+
const output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
252
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
253
|
+
expect(output).not.toContain('[UPDATED]');
|
|
254
|
+
// Pull should skip this document
|
|
255
|
+
const originalContent = fs.readFileSync(localFile, 'utf-8');
|
|
256
|
+
await pullDocuments(false, true);
|
|
257
|
+
const afterPullContent = fs.readFileSync(localFile, 'utf-8');
|
|
258
|
+
// File should be unchanged
|
|
259
|
+
expect(afterPullContent).toBe(originalContent);
|
|
260
|
+
});
|
|
261
|
+
it('should handle rapid state changes: NEW → UP-TO-DATE → UPDATED → UP-TO-DATE', async () => {
|
|
262
|
+
config.saveConfig(mockConfig());
|
|
263
|
+
vi.spyOn(tokenRefresh, 'ensureValidToken').mockResolvedValue(true);
|
|
264
|
+
const mockSupabase = createMockSupabaseClient();
|
|
265
|
+
// State 1: NEW
|
|
266
|
+
const time1 = new Date('2024-01-01T00:00:00Z');
|
|
267
|
+
const doc1 = mockDocumentRecord({
|
|
268
|
+
id: 'doc-123',
|
|
269
|
+
title: 'Rapid Changes',
|
|
270
|
+
updated_at: time1.toISOString()
|
|
271
|
+
});
|
|
272
|
+
mockSupabase.from.mockReturnValue({
|
|
273
|
+
select: vi.fn().mockReturnThis(),
|
|
274
|
+
order: vi.fn().mockResolvedValue({ data: [doc1], error: null })
|
|
275
|
+
});
|
|
276
|
+
const content1 = mockBlockNoteContent('Version 1');
|
|
277
|
+
const blob1 = new Blob([JSON.stringify(content1)], { type: 'application/json' });
|
|
278
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
279
|
+
download: vi.fn().mockResolvedValue({ data: blob1, error: null })
|
|
280
|
+
});
|
|
281
|
+
vi.spyOn(supabaseClient, 'getSupabaseClient').mockReturnValue(mockSupabase);
|
|
282
|
+
// Step 1: NEW
|
|
283
|
+
await listDocuments();
|
|
284
|
+
let output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
285
|
+
expect(output).toContain('[NEW]');
|
|
286
|
+
// Pull → UP-TO-DATE
|
|
287
|
+
await pullDocuments(false, true);
|
|
288
|
+
consoleSpy.mockClear();
|
|
289
|
+
await listDocuments();
|
|
290
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
291
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
292
|
+
// Remote updates → UPDATED
|
|
293
|
+
const time2 = new Date('2024-01-02T00:00:00Z');
|
|
294
|
+
const doc2 = mockDocumentRecord({
|
|
295
|
+
id: 'doc-123',
|
|
296
|
+
title: 'Rapid Changes',
|
|
297
|
+
updated_at: time2.toISOString()
|
|
298
|
+
});
|
|
299
|
+
mockSupabase.from.mockReturnValue({
|
|
300
|
+
select: vi.fn().mockReturnThis(),
|
|
301
|
+
order: vi.fn().mockResolvedValue({ data: [doc2], error: null })
|
|
302
|
+
});
|
|
303
|
+
const content2 = mockBlockNoteContent('Version 2');
|
|
304
|
+
const blob2 = new Blob([JSON.stringify(content2)], { type: 'application/json' });
|
|
305
|
+
mockSupabase.storage.from.mockReturnValue({
|
|
306
|
+
download: vi.fn().mockResolvedValue({ data: blob2, error: null })
|
|
307
|
+
});
|
|
308
|
+
consoleSpy.mockClear();
|
|
309
|
+
await listDocuments();
|
|
310
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
311
|
+
expect(output).toContain('[UPDATED]');
|
|
312
|
+
// Pull again → UP-TO-DATE
|
|
313
|
+
await pullDocuments(false, true);
|
|
314
|
+
consoleSpy.mockClear();
|
|
315
|
+
await listDocuments();
|
|
316
|
+
output = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
317
|
+
expect(output).toContain('[UP-TO-DATE]');
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
//# sourceMappingURL=state-transitions.test.js.map
|