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.
Files changed (58) hide show
  1. package/README.md +37 -3
  2. package/dist/__tests__/commands/list.test.d.ts +2 -0
  3. package/dist/__tests__/commands/list.test.d.ts.map +1 -0
  4. package/dist/__tests__/commands/list.test.js +109 -0
  5. package/dist/__tests__/commands/list.test.js.map +1 -0
  6. package/dist/__tests__/commands/pull.test.js +152 -57
  7. package/dist/__tests__/commands/pull.test.js.map +1 -1
  8. package/dist/__tests__/integration/complete-user-journeys.test.d.ts +2 -0
  9. package/dist/__tests__/integration/complete-user-journeys.test.d.ts.map +1 -0
  10. package/dist/__tests__/integration/complete-user-journeys.test.js +348 -0
  11. package/dist/__tests__/integration/complete-user-journeys.test.js.map +1 -0
  12. package/dist/__tests__/integration/config-integration.test.d.ts +2 -0
  13. package/dist/__tests__/integration/config-integration.test.d.ts.map +1 -0
  14. package/dist/__tests__/integration/config-integration.test.js +151 -0
  15. package/dist/__tests__/integration/config-integration.test.js.map +1 -0
  16. package/dist/__tests__/integration/data-integrity.test.d.ts +2 -0
  17. package/dist/__tests__/integration/data-integrity.test.d.ts.map +1 -0
  18. package/dist/__tests__/integration/data-integrity.test.js +296 -0
  19. package/dist/__tests__/integration/data-integrity.test.js.map +1 -0
  20. package/dist/__tests__/integration/edge-cases-advanced.test.d.ts +2 -0
  21. package/dist/__tests__/integration/edge-cases-advanced.test.d.ts.map +1 -0
  22. package/dist/__tests__/integration/edge-cases-advanced.test.js +286 -0
  23. package/dist/__tests__/integration/edge-cases-advanced.test.js.map +1 -0
  24. package/dist/__tests__/integration/error-recovery.test.d.ts +2 -0
  25. package/dist/__tests__/integration/error-recovery.test.d.ts.map +1 -0
  26. package/dist/__tests__/integration/error-recovery.test.js +277 -0
  27. package/dist/__tests__/integration/error-recovery.test.js.map +1 -0
  28. package/dist/__tests__/integration/filesystem-edge-cases.test.d.ts +2 -0
  29. package/dist/__tests__/integration/filesystem-edge-cases.test.d.ts.map +1 -0
  30. package/dist/__tests__/integration/filesystem-edge-cases.test.js +218 -0
  31. package/dist/__tests__/integration/filesystem-edge-cases.test.js.map +1 -0
  32. package/dist/__tests__/integration/filtering-operations.test.d.ts +2 -0
  33. package/dist/__tests__/integration/filtering-operations.test.d.ts.map +1 -0
  34. package/dist/__tests__/integration/filtering-operations.test.js +278 -0
  35. package/dist/__tests__/integration/filtering-operations.test.js.map +1 -0
  36. package/dist/__tests__/integration/state-transitions.test.d.ts +2 -0
  37. package/dist/__tests__/integration/state-transitions.test.d.ts.map +1 -0
  38. package/dist/__tests__/integration/state-transitions.test.js +320 -0
  39. package/dist/__tests__/integration/state-transitions.test.js.map +1 -0
  40. package/dist/commands/list.d.ts +4 -0
  41. package/dist/commands/list.d.ts.map +1 -0
  42. package/dist/commands/list.js +19 -0
  43. package/dist/commands/list.js.map +1 -0
  44. package/dist/commands/pull.d.ts.map +1 -1
  45. package/dist/commands/pull.js +5 -3
  46. package/dist/commands/pull.js.map +1 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +2 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/utils/listOperations.d.ts +5 -0
  51. package/dist/utils/listOperations.d.ts.map +1 -0
  52. package/dist/utils/listOperations.js +138 -0
  53. package/dist/utils/listOperations.js.map +1 -0
  54. package/dist/utils/pullOperations.d.ts +3 -2
  55. package/dist/utils/pullOperations.d.ts.map +1 -1
  56. package/dist/utils/pullOperations.js +52 -8
  57. package/dist/utils/pullOperations.js.map +1 -1
  58. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=state-transitions.test.d.ts.map
@@ -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