@smallironman/mcp-memory-keeper 0.12.2-fork1
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/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +1054 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +411 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +291 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +305 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4425 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +2017 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +780 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- package/package.json +85 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const globals_1 = require("@jest/globals");
|
|
37
|
+
const database_1 = require("../../utils/database");
|
|
38
|
+
const ContextRepository_1 = require("../../repositories/ContextRepository");
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const uuid_1 = require("uuid");
|
|
43
|
+
(0, globals_1.describe)('Enhanced Context Timeline Handler Integration Tests', () => {
|
|
44
|
+
let dbManager;
|
|
45
|
+
let tempDbPath;
|
|
46
|
+
let db;
|
|
47
|
+
let contextRepo;
|
|
48
|
+
let testSessionId;
|
|
49
|
+
(0, globals_1.beforeEach)(() => {
|
|
50
|
+
tempDbPath = path.join(os.tmpdir(), `test-enhanced-timeline-${Date.now()}.db`);
|
|
51
|
+
dbManager = new database_1.DatabaseManager({
|
|
52
|
+
filename: tempDbPath,
|
|
53
|
+
maxSize: 10 * 1024 * 1024,
|
|
54
|
+
walMode: true,
|
|
55
|
+
});
|
|
56
|
+
db = dbManager.getDatabase();
|
|
57
|
+
contextRepo = new ContextRepository_1.ContextRepository(dbManager);
|
|
58
|
+
// Create test session
|
|
59
|
+
testSessionId = (0, uuid_1.v4)();
|
|
60
|
+
db.prepare('INSERT INTO sessions (id, name) VALUES (?, ?)').run(testSessionId, 'Test Session');
|
|
61
|
+
});
|
|
62
|
+
(0, globals_1.afterEach)(() => {
|
|
63
|
+
dbManager.close();
|
|
64
|
+
try {
|
|
65
|
+
fs.unlinkSync(tempDbPath);
|
|
66
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
67
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
68
|
+
}
|
|
69
|
+
catch (_e) {
|
|
70
|
+
// Ignore
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
function createTestDataWithTimeline() {
|
|
74
|
+
// TIMEZONE-SAFE PATTERN: Use fixed UTC dates to ensure consistent behavior
|
|
75
|
+
// across all environments regardless of system timezone
|
|
76
|
+
// Create a fixed reference point in UTC (2025-06-20 12:00:00 UTC)
|
|
77
|
+
// This ensures consistent timeline grouping in all environments
|
|
78
|
+
const baseDate = new Date('2025-06-20T12:00:00.000Z');
|
|
79
|
+
// Create calendar-day-aligned dates in UTC by using UTC methods
|
|
80
|
+
const today = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate(), 12, 0, 0));
|
|
81
|
+
const yesterday = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 1, 12, 0, 0));
|
|
82
|
+
const threeDaysAgo = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 3, 12, 0, 0));
|
|
83
|
+
const fiveDaysAgo = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 5, 12, 0, 0));
|
|
84
|
+
const sevenDaysAgo = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 7, 12, 0, 0));
|
|
85
|
+
// Create items across different time periods
|
|
86
|
+
const items = [
|
|
87
|
+
// Today - 6 items (all at noon on same day)
|
|
88
|
+
{ time: new Date(today.getTime() + 1 * 60 * 60 * 1000), category: 'task', priority: 'high' },
|
|
89
|
+
{
|
|
90
|
+
time: new Date(today.getTime() + 2 * 60 * 60 * 1000),
|
|
91
|
+
category: 'task',
|
|
92
|
+
priority: 'normal',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
time: new Date(today.getTime() + 3 * 60 * 60 * 1000),
|
|
96
|
+
category: 'note',
|
|
97
|
+
priority: 'normal',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
time: new Date(today.getTime() + 4 * 60 * 60 * 1000),
|
|
101
|
+
category: 'decision',
|
|
102
|
+
priority: 'high',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
time: new Date(today.getTime() + 5 * 60 * 60 * 1000),
|
|
106
|
+
category: 'progress',
|
|
107
|
+
priority: 'normal',
|
|
108
|
+
},
|
|
109
|
+
{ time: new Date(today.getTime() + 6 * 60 * 60 * 1000), category: 'task', priority: 'low' },
|
|
110
|
+
// Yesterday - 3 items (all on previous day)
|
|
111
|
+
{
|
|
112
|
+
time: new Date(yesterday.getTime() + 1 * 60 * 60 * 1000),
|
|
113
|
+
category: 'task',
|
|
114
|
+
priority: 'high',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
time: new Date(yesterday.getTime() + 2 * 60 * 60 * 1000),
|
|
118
|
+
category: 'note',
|
|
119
|
+
priority: 'normal',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
time: new Date(yesterday.getTime() + 3 * 60 * 60 * 1000),
|
|
123
|
+
category: 'progress',
|
|
124
|
+
priority: 'low',
|
|
125
|
+
},
|
|
126
|
+
// 3 days ago - 1 item
|
|
127
|
+
{
|
|
128
|
+
time: new Date(threeDaysAgo.getTime() + 1 * 60 * 60 * 1000),
|
|
129
|
+
category: 'decision',
|
|
130
|
+
priority: 'high',
|
|
131
|
+
},
|
|
132
|
+
// 5 days ago - 2 items
|
|
133
|
+
{
|
|
134
|
+
time: new Date(fiveDaysAgo.getTime() + 1 * 60 * 60 * 1000),
|
|
135
|
+
category: 'task',
|
|
136
|
+
priority: 'normal',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
time: new Date(fiveDaysAgo.getTime() + 2 * 60 * 60 * 1000),
|
|
140
|
+
category: 'note',
|
|
141
|
+
priority: 'normal',
|
|
142
|
+
},
|
|
143
|
+
// 7 days ago - 4 items (all on same day)
|
|
144
|
+
{
|
|
145
|
+
time: new Date(sevenDaysAgo.getTime() + 1 * 60 * 60 * 1000),
|
|
146
|
+
category: 'progress',
|
|
147
|
+
priority: 'high',
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
time: new Date(sevenDaysAgo.getTime() + 2 * 60 * 60 * 1000),
|
|
151
|
+
category: 'task',
|
|
152
|
+
priority: 'normal',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
time: new Date(sevenDaysAgo.getTime() + 3 * 60 * 60 * 1000),
|
|
156
|
+
category: 'decision',
|
|
157
|
+
priority: 'low',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
time: new Date(sevenDaysAgo.getTime() + 4 * 60 * 60 * 1000),
|
|
161
|
+
category: 'note',
|
|
162
|
+
priority: 'normal',
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
const stmt = db.prepare(`
|
|
166
|
+
INSERT INTO context_items (
|
|
167
|
+
id, session_id, key, value, category, priority, channel, created_at, updated_at, size
|
|
168
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
+
`);
|
|
170
|
+
items.forEach((item, index) => {
|
|
171
|
+
const key = `item.${item.time.toISOString().split('T')[0]}.${index}`;
|
|
172
|
+
const value = `Test item created at ${item.time.toISOString()}`;
|
|
173
|
+
stmt.run((0, uuid_1.v4)(), testSessionId, key, value, item.category, item.priority, 'test-channel', item.time.toISOString(), item.time.toISOString(), Buffer.byteLength(value, 'utf8'));
|
|
174
|
+
});
|
|
175
|
+
return items;
|
|
176
|
+
}
|
|
177
|
+
(0, globals_1.describe)('minItemsPerPeriod Tests', () => {
|
|
178
|
+
(0, globals_1.beforeEach)(() => {
|
|
179
|
+
createTestDataWithTimeline();
|
|
180
|
+
});
|
|
181
|
+
(0, globals_1.it)('should filter periods with fewer items than minItemsPerPeriod', () => {
|
|
182
|
+
const timeline = contextRepo.getTimelineData({
|
|
183
|
+
sessionId: testSessionId,
|
|
184
|
+
groupBy: 'day',
|
|
185
|
+
minItemsPerPeriod: 3,
|
|
186
|
+
});
|
|
187
|
+
// Only today (6 items), yesterday (3 items), and 7 days ago (4 items) should appear
|
|
188
|
+
(0, globals_1.expect)(timeline.length).toBe(3);
|
|
189
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 3)).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
(0, globals_1.it)('should include all periods when minItemsPerPeriod is 0', () => {
|
|
192
|
+
const timeline = contextRepo.getTimelineData({
|
|
193
|
+
sessionId: testSessionId,
|
|
194
|
+
groupBy: 'day',
|
|
195
|
+
minItemsPerPeriod: 0,
|
|
196
|
+
});
|
|
197
|
+
// Should include all 5 periods that have data:
|
|
198
|
+
// - Today (2025-06-20): 6 items
|
|
199
|
+
// - Yesterday (2025-06-19): 3 items
|
|
200
|
+
// - 3 days ago (2025-06-17): 1 item
|
|
201
|
+
// - 5 days ago (2025-06-15): 2 items
|
|
202
|
+
// - 7 days ago (2025-06-13): 4 items
|
|
203
|
+
(0, globals_1.expect)(timeline.length).toBe(5);
|
|
204
|
+
});
|
|
205
|
+
(0, globals_1.it)('should handle negative minItemsPerPeriod by treating as 0', () => {
|
|
206
|
+
const timeline = contextRepo.getTimelineData({
|
|
207
|
+
sessionId: testSessionId,
|
|
208
|
+
groupBy: 'day',
|
|
209
|
+
minItemsPerPeriod: -5,
|
|
210
|
+
});
|
|
211
|
+
// Should include all periods (same as 0)
|
|
212
|
+
(0, globals_1.expect)(timeline.length).toBe(5);
|
|
213
|
+
});
|
|
214
|
+
(0, globals_1.it)('should work with category filters and minItemsPerPeriod', () => {
|
|
215
|
+
const timeline = contextRepo.getTimelineData({
|
|
216
|
+
sessionId: testSessionId,
|
|
217
|
+
groupBy: 'day',
|
|
218
|
+
categories: ['task'],
|
|
219
|
+
minItemsPerPeriod: 2,
|
|
220
|
+
});
|
|
221
|
+
// Only today (3 task items) and 7 days ago (1 task, but need to verify) should qualify
|
|
222
|
+
// Actually checking: today has 3 tasks, yesterday has 1 task, 7 days ago has 1 task
|
|
223
|
+
// So only today qualifies with 2+ task items
|
|
224
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 2)).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
(0, globals_1.it)('should work with hour grouping and minItemsPerPeriod', () => {
|
|
227
|
+
const timeline = contextRepo.getTimelineData({
|
|
228
|
+
sessionId: testSessionId,
|
|
229
|
+
groupBy: 'hour',
|
|
230
|
+
minItemsPerPeriod: 1,
|
|
231
|
+
relativeTime: 'today',
|
|
232
|
+
});
|
|
233
|
+
// Should only show hours that have at least 1 item
|
|
234
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 1)).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
(0, globals_1.it)('should include item details when requested with minItemsPerPeriod filter', () => {
|
|
237
|
+
const timeline = contextRepo.getTimelineData({
|
|
238
|
+
sessionId: testSessionId,
|
|
239
|
+
groupBy: 'day',
|
|
240
|
+
minItemsPerPeriod: 3,
|
|
241
|
+
includeItems: true,
|
|
242
|
+
itemsPerPeriod: 5,
|
|
243
|
+
});
|
|
244
|
+
// Check that periods have items attached
|
|
245
|
+
timeline.forEach((period) => {
|
|
246
|
+
(0, globals_1.expect)(period.items).toBeDefined();
|
|
247
|
+
(0, globals_1.expect)(Array.isArray(period.items)).toBe(true);
|
|
248
|
+
(0, globals_1.expect)(period.items.length).toBeLessThanOrEqual(5);
|
|
249
|
+
(0, globals_1.expect)(period.count).toBeGreaterThanOrEqual(3);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
(0, globals_1.it)('should work with relative time and minItemsPerPeriod', () => {
|
|
253
|
+
const timeline = contextRepo.getTimelineData({
|
|
254
|
+
sessionId: testSessionId,
|
|
255
|
+
relativeTime: '7 days ago',
|
|
256
|
+
groupBy: 'day',
|
|
257
|
+
minItemsPerPeriod: 2,
|
|
258
|
+
});
|
|
259
|
+
// Should include periods from last 7 days with 2+ items
|
|
260
|
+
timeline.forEach((period) => {
|
|
261
|
+
(0, globals_1.expect)(period.count).toBeGreaterThanOrEqual(2);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
(0, globals_1.describe)('showEmpty Tests', () => {
|
|
266
|
+
(0, globals_1.beforeEach)(() => {
|
|
267
|
+
createTestDataWithTimeline();
|
|
268
|
+
});
|
|
269
|
+
(0, globals_1.it)('should generate empty periods when showEmpty is true', () => {
|
|
270
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
271
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
272
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 10, 12, 0, 0));
|
|
273
|
+
const timeline = contextRepo.getTimelineData({
|
|
274
|
+
sessionId: testSessionId,
|
|
275
|
+
startDate: startDate.toISOString(),
|
|
276
|
+
endDate: endDate.toISOString(),
|
|
277
|
+
groupBy: 'day',
|
|
278
|
+
showEmpty: true,
|
|
279
|
+
});
|
|
280
|
+
// Should have 11 periods (10 days + today)
|
|
281
|
+
(0, globals_1.expect)(timeline.length).toBe(11);
|
|
282
|
+
// Check that empty periods have count = 0
|
|
283
|
+
const emptyPeriods = timeline.filter((p) => p.count === 0);
|
|
284
|
+
(0, globals_1.expect)(emptyPeriods.length).toBeGreaterThan(0);
|
|
285
|
+
emptyPeriods.forEach((period) => {
|
|
286
|
+
(0, globals_1.expect)(period.count).toBe(0);
|
|
287
|
+
(0, globals_1.expect)(period.items).toEqual([]);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
(0, globals_1.it)('should handle showEmpty with hour grouping', () => {
|
|
291
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
292
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
293
|
+
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
|
|
294
|
+
const timeline = contextRepo.getTimelineData({
|
|
295
|
+
sessionId: testSessionId,
|
|
296
|
+
startDate: startDate.toISOString(),
|
|
297
|
+
endDate: endDate.toISOString(),
|
|
298
|
+
groupBy: 'hour',
|
|
299
|
+
showEmpty: true,
|
|
300
|
+
});
|
|
301
|
+
// Should have 25 periods (24 hours + current hour)
|
|
302
|
+
(0, globals_1.expect)(timeline.length).toBe(25);
|
|
303
|
+
});
|
|
304
|
+
(0, globals_1.it)('should handle showEmpty with week grouping', () => {
|
|
305
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
306
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
307
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 28, 12, 0, 0));
|
|
308
|
+
const timeline = contextRepo.getTimelineData({
|
|
309
|
+
sessionId: testSessionId,
|
|
310
|
+
startDate: startDate.toISOString(),
|
|
311
|
+
endDate: endDate.toISOString(),
|
|
312
|
+
groupBy: 'week',
|
|
313
|
+
showEmpty: true,
|
|
314
|
+
});
|
|
315
|
+
// Should have 5 periods (4 full weeks + current week)
|
|
316
|
+
(0, globals_1.expect)(timeline.length).toBeGreaterThanOrEqual(4);
|
|
317
|
+
(0, globals_1.expect)(timeline.length).toBeLessThanOrEqual(5);
|
|
318
|
+
});
|
|
319
|
+
(0, globals_1.it)('should include empty periods with categories filter', () => {
|
|
320
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
321
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
322
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 5, 12, 0, 0));
|
|
323
|
+
const timeline = contextRepo.getTimelineData({
|
|
324
|
+
sessionId: testSessionId,
|
|
325
|
+
startDate: startDate.toISOString(),
|
|
326
|
+
endDate: endDate.toISOString(),
|
|
327
|
+
groupBy: 'day',
|
|
328
|
+
categories: ['task'],
|
|
329
|
+
showEmpty: true,
|
|
330
|
+
});
|
|
331
|
+
// Should have 6 periods regardless of task items
|
|
332
|
+
(0, globals_1.expect)(timeline.length).toBe(6);
|
|
333
|
+
});
|
|
334
|
+
(0, globals_1.it)('should handle showEmpty with relative time', () => {
|
|
335
|
+
const timeline = contextRepo.getTimelineData({
|
|
336
|
+
sessionId: testSessionId,
|
|
337
|
+
relativeTime: '3 days ago',
|
|
338
|
+
groupBy: 'day',
|
|
339
|
+
showEmpty: true,
|
|
340
|
+
});
|
|
341
|
+
// Should have 4 periods (3 days ago to today)
|
|
342
|
+
(0, globals_1.expect)(timeline.length).toBe(4);
|
|
343
|
+
});
|
|
344
|
+
(0, globals_1.it)('should enforce reasonable limits on empty period generation', () => {
|
|
345
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
346
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
347
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear() - 1, endDate.getUTCMonth(), endDate.getUTCDate(), 12, 0, 0));
|
|
348
|
+
const timeline = contextRepo.getTimelineData({
|
|
349
|
+
sessionId: testSessionId,
|
|
350
|
+
startDate: startDate.toISOString(),
|
|
351
|
+
endDate: endDate.toISOString(),
|
|
352
|
+
groupBy: 'day',
|
|
353
|
+
showEmpty: true,
|
|
354
|
+
});
|
|
355
|
+
// Should enforce a reasonable limit (e.g., 365 days max)
|
|
356
|
+
(0, globals_1.expect)(timeline.length).toBeLessThanOrEqual(365);
|
|
357
|
+
});
|
|
358
|
+
(0, globals_1.it)('should include items in non-empty periods when showEmpty is true', () => {
|
|
359
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
360
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
361
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 3, 12, 0, 0));
|
|
362
|
+
const timeline = contextRepo.getTimelineData({
|
|
363
|
+
sessionId: testSessionId,
|
|
364
|
+
startDate: startDate.toISOString(),
|
|
365
|
+
endDate: endDate.toISOString(),
|
|
366
|
+
groupBy: 'day',
|
|
367
|
+
showEmpty: true,
|
|
368
|
+
includeItems: true,
|
|
369
|
+
});
|
|
370
|
+
// Find periods with data
|
|
371
|
+
const nonEmptyPeriods = timeline.filter((p) => p.count > 0);
|
|
372
|
+
(0, globals_1.expect)(nonEmptyPeriods.length).toBeGreaterThan(0);
|
|
373
|
+
nonEmptyPeriods.forEach((period) => {
|
|
374
|
+
(0, globals_1.expect)(period.items).toBeDefined();
|
|
375
|
+
(0, globals_1.expect)(period.items.length).toBeGreaterThan(0);
|
|
376
|
+
});
|
|
377
|
+
// Empty periods should have empty items array
|
|
378
|
+
const emptyPeriods = timeline.filter((p) => p.count === 0);
|
|
379
|
+
emptyPeriods.forEach((period) => {
|
|
380
|
+
(0, globals_1.expect)(period.items).toEqual([]);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
(0, globals_1.describe)('Parameter Interaction Tests', () => {
|
|
385
|
+
(0, globals_1.beforeEach)(() => {
|
|
386
|
+
createTestDataWithTimeline();
|
|
387
|
+
});
|
|
388
|
+
(0, globals_1.it)('should have showEmpty override minItemsPerPeriod', () => {
|
|
389
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
390
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
391
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 5, 12, 0, 0));
|
|
392
|
+
const timeline = contextRepo.getTimelineData({
|
|
393
|
+
sessionId: testSessionId,
|
|
394
|
+
startDate: startDate.toISOString(),
|
|
395
|
+
endDate: endDate.toISOString(),
|
|
396
|
+
groupBy: 'day',
|
|
397
|
+
showEmpty: true,
|
|
398
|
+
minItemsPerPeriod: 3,
|
|
399
|
+
});
|
|
400
|
+
// Should show all 6 days, including empty ones
|
|
401
|
+
(0, globals_1.expect)(timeline.length).toBe(6);
|
|
402
|
+
// Should include periods with 0 items despite minItemsPerPeriod
|
|
403
|
+
const emptyPeriods = timeline.filter((p) => p.count === 0);
|
|
404
|
+
(0, globals_1.expect)(emptyPeriods.length).toBeGreaterThan(0);
|
|
405
|
+
});
|
|
406
|
+
(0, globals_1.it)('should work with all parameters combined', () => {
|
|
407
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
408
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
409
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 7, 12, 0, 0));
|
|
410
|
+
const timeline = contextRepo.getTimelineData({
|
|
411
|
+
sessionId: testSessionId,
|
|
412
|
+
startDate: startDate.toISOString(),
|
|
413
|
+
endDate: endDate.toISOString(),
|
|
414
|
+
groupBy: 'day',
|
|
415
|
+
categories: ['task', 'progress'],
|
|
416
|
+
showEmpty: true,
|
|
417
|
+
minItemsPerPeriod: 2,
|
|
418
|
+
includeItems: true,
|
|
419
|
+
itemsPerPeriod: 3,
|
|
420
|
+
});
|
|
421
|
+
// Should have 8 days total
|
|
422
|
+
(0, globals_1.expect)(timeline.length).toBe(8);
|
|
423
|
+
// Check various aspects
|
|
424
|
+
timeline.forEach((period) => {
|
|
425
|
+
(0, globals_1.expect)(period).toHaveProperty('period');
|
|
426
|
+
(0, globals_1.expect)(period).toHaveProperty('count');
|
|
427
|
+
(0, globals_1.expect)(period).toHaveProperty('items');
|
|
428
|
+
if (period.count > 0) {
|
|
429
|
+
// Non-empty periods should respect itemsPerPeriod
|
|
430
|
+
(0, globals_1.expect)(period.items.length).toBeLessThanOrEqual(3);
|
|
431
|
+
// Items should only be from specified categories
|
|
432
|
+
period.items.forEach((item) => {
|
|
433
|
+
(0, globals_1.expect)(['task', 'progress']).toContain(item.category);
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
(0, globals_1.it)('should handle showEmpty false with minItemsPerPeriod', () => {
|
|
439
|
+
const timeline = contextRepo.getTimelineData({
|
|
440
|
+
sessionId: testSessionId,
|
|
441
|
+
groupBy: 'day',
|
|
442
|
+
showEmpty: false,
|
|
443
|
+
minItemsPerPeriod: 2,
|
|
444
|
+
});
|
|
445
|
+
// Should only show periods with 2+ items
|
|
446
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 2)).toBe(true);
|
|
447
|
+
// Should not include any empty periods
|
|
448
|
+
(0, globals_1.expect)(timeline.every((period) => period.count > 0)).toBe(true);
|
|
449
|
+
});
|
|
450
|
+
(0, globals_1.it)('should respect date ranges with both new parameters', () => {
|
|
451
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
452
|
+
const baseDate = new Date('2025-06-20T12:00:00.000Z');
|
|
453
|
+
const startDate = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 2, 0, 0, 0));
|
|
454
|
+
const endDate = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - 1, 23, 59, 59, 999));
|
|
455
|
+
const timeline = contextRepo.getTimelineData({
|
|
456
|
+
sessionId: testSessionId,
|
|
457
|
+
startDate: startDate.toISOString(),
|
|
458
|
+
endDate: endDate.toISOString(),
|
|
459
|
+
groupBy: 'day',
|
|
460
|
+
showEmpty: true,
|
|
461
|
+
minItemsPerPeriod: 5,
|
|
462
|
+
});
|
|
463
|
+
// Should show exactly 2 days
|
|
464
|
+
(0, globals_1.expect)(timeline.length).toBe(2);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
(0, globals_1.describe)('Edge Cases and Validation Tests', () => {
|
|
468
|
+
(0, globals_1.beforeEach)(() => {
|
|
469
|
+
createTestDataWithTimeline();
|
|
470
|
+
});
|
|
471
|
+
(0, globals_1.it)('should handle minItemsPerPeriod larger than any period count', () => {
|
|
472
|
+
const timeline = contextRepo.getTimelineData({
|
|
473
|
+
sessionId: testSessionId,
|
|
474
|
+
groupBy: 'day',
|
|
475
|
+
minItemsPerPeriod: 100,
|
|
476
|
+
});
|
|
477
|
+
// Should return empty array since no period has 100+ items
|
|
478
|
+
(0, globals_1.expect)(timeline).toEqual([]);
|
|
479
|
+
});
|
|
480
|
+
(0, globals_1.it)('should handle showEmpty with no date range specified', () => {
|
|
481
|
+
// Without date range, showEmpty should be ignored
|
|
482
|
+
const timeline = contextRepo.getTimelineData({
|
|
483
|
+
sessionId: testSessionId,
|
|
484
|
+
groupBy: 'day',
|
|
485
|
+
showEmpty: true,
|
|
486
|
+
});
|
|
487
|
+
// Should only return periods with data
|
|
488
|
+
(0, globals_1.expect)(timeline.every((period) => period.count > 0)).toBe(true);
|
|
489
|
+
});
|
|
490
|
+
(0, globals_1.it)('should handle invalid date ranges gracefully', () => {
|
|
491
|
+
const timeline = contextRepo.getTimelineData({
|
|
492
|
+
sessionId: testSessionId,
|
|
493
|
+
startDate: '2025-01-01',
|
|
494
|
+
endDate: '2024-01-01', // End before start
|
|
495
|
+
groupBy: 'day',
|
|
496
|
+
showEmpty: true,
|
|
497
|
+
});
|
|
498
|
+
// Should return empty array or handle gracefully
|
|
499
|
+
(0, globals_1.expect)(timeline).toEqual([]);
|
|
500
|
+
});
|
|
501
|
+
(0, globals_1.it)('should handle very large minItemsPerPeriod values', () => {
|
|
502
|
+
const timeline = contextRepo.getTimelineData({
|
|
503
|
+
sessionId: testSessionId,
|
|
504
|
+
groupBy: 'day',
|
|
505
|
+
minItemsPerPeriod: Number.MAX_SAFE_INTEGER,
|
|
506
|
+
});
|
|
507
|
+
// Should return empty array
|
|
508
|
+
(0, globals_1.expect)(timeline).toEqual([]);
|
|
509
|
+
});
|
|
510
|
+
(0, globals_1.it)('should handle fractional minItemsPerPeriod by rounding', () => {
|
|
511
|
+
const timeline = contextRepo.getTimelineData({
|
|
512
|
+
sessionId: testSessionId,
|
|
513
|
+
groupBy: 'day',
|
|
514
|
+
minItemsPerPeriod: 2.7, // Should be treated as 3
|
|
515
|
+
});
|
|
516
|
+
// Periods should have at least 3 items
|
|
517
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 3)).toBe(true);
|
|
518
|
+
});
|
|
519
|
+
(0, globals_1.it)('should handle showEmpty with very narrow time windows', () => {
|
|
520
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
521
|
+
const startDate = new Date('2025-06-20T12:00:00.000Z');
|
|
522
|
+
const endDate = new Date(startDate.getTime() + 60 * 60 * 1000); // 1 hour later
|
|
523
|
+
const timeline = contextRepo.getTimelineData({
|
|
524
|
+
sessionId: testSessionId,
|
|
525
|
+
startDate: startDate.toISOString(),
|
|
526
|
+
endDate: endDate.toISOString(),
|
|
527
|
+
groupBy: 'hour',
|
|
528
|
+
showEmpty: true,
|
|
529
|
+
});
|
|
530
|
+
// Should have 2 hour periods
|
|
531
|
+
(0, globals_1.expect)(timeline.length).toBe(2);
|
|
532
|
+
});
|
|
533
|
+
(0, globals_1.it)('should maintain sort order with new parameters', () => {
|
|
534
|
+
const timeline = contextRepo.getTimelineData({
|
|
535
|
+
sessionId: testSessionId,
|
|
536
|
+
groupBy: 'day',
|
|
537
|
+
showEmpty: false,
|
|
538
|
+
minItemsPerPeriod: 1,
|
|
539
|
+
});
|
|
540
|
+
// Verify descending order by period
|
|
541
|
+
for (let i = 1; i < timeline.length; i++) {
|
|
542
|
+
(0, globals_1.expect)(timeline[i - 1].period >= timeline[i].period).toBe(true);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
(0, globals_1.describe)('Performance Tests', () => {
|
|
547
|
+
(0, globals_1.it)('should handle large datasets efficiently with minItemsPerPeriod', () => {
|
|
548
|
+
// Create many items
|
|
549
|
+
const stmt = db.prepare(`
|
|
550
|
+
INSERT INTO context_items (
|
|
551
|
+
id, session_id, key, value, category, channel, created_at, size
|
|
552
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
553
|
+
`);
|
|
554
|
+
const batchSize = 1000;
|
|
555
|
+
const baseDate = new Date('2025-06-20T12:00:00.000Z');
|
|
556
|
+
for (let i = 0; i < batchSize; i++) {
|
|
557
|
+
const daysAgo = Math.floor(Math.random() * 30);
|
|
558
|
+
const date = new Date(Date.UTC(baseDate.getUTCFullYear(), baseDate.getUTCMonth(), baseDate.getUTCDate() - daysAgo, 12, 0, 0));
|
|
559
|
+
stmt.run((0, uuid_1.v4)(), testSessionId, `perf.test.${i}`, `Performance test item ${i}`, 'performance', 'test-channel', date.toISOString(), 20);
|
|
560
|
+
}
|
|
561
|
+
const startTime = Date.now();
|
|
562
|
+
const timeline = contextRepo.getTimelineData({
|
|
563
|
+
sessionId: testSessionId,
|
|
564
|
+
groupBy: 'day',
|
|
565
|
+
minItemsPerPeriod: 10,
|
|
566
|
+
});
|
|
567
|
+
const endTime = Date.now();
|
|
568
|
+
// Should complete within reasonable time
|
|
569
|
+
(0, globals_1.expect)(endTime - startTime).toBeLessThan(500);
|
|
570
|
+
// Should only show days with 10+ items
|
|
571
|
+
(0, globals_1.expect)(timeline.every((period) => period.count >= 10)).toBe(true);
|
|
572
|
+
});
|
|
573
|
+
(0, globals_1.it)('should handle showEmpty efficiently for large date ranges', () => {
|
|
574
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
575
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
576
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 90, 12, 0, 0));
|
|
577
|
+
const startTime = Date.now();
|
|
578
|
+
const timeline = contextRepo.getTimelineData({
|
|
579
|
+
sessionId: testSessionId,
|
|
580
|
+
startDate: startDate.toISOString(),
|
|
581
|
+
endDate: endDate.toISOString(),
|
|
582
|
+
groupBy: 'day',
|
|
583
|
+
showEmpty: true,
|
|
584
|
+
});
|
|
585
|
+
const endTime = Date.now();
|
|
586
|
+
// Should complete within reasonable time
|
|
587
|
+
(0, globals_1.expect)(endTime - startTime).toBeLessThan(1000);
|
|
588
|
+
// Should have ~91 periods
|
|
589
|
+
(0, globals_1.expect)(timeline.length).toBeGreaterThanOrEqual(90);
|
|
590
|
+
(0, globals_1.expect)(timeline.length).toBeLessThanOrEqual(92);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
(0, globals_1.describe)('Backward Compatibility Tests', () => {
|
|
594
|
+
(0, globals_1.beforeEach)(() => {
|
|
595
|
+
createTestDataWithTimeline();
|
|
596
|
+
});
|
|
597
|
+
(0, globals_1.it)('should work without new parameters (existing behavior)', () => {
|
|
598
|
+
const timeline = contextRepo.getTimelineData({
|
|
599
|
+
sessionId: testSessionId,
|
|
600
|
+
groupBy: 'day',
|
|
601
|
+
});
|
|
602
|
+
// Should return all periods with data
|
|
603
|
+
(0, globals_1.expect)(timeline.length).toBeGreaterThan(0);
|
|
604
|
+
(0, globals_1.expect)(timeline.every((period) => period.count > 0)).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
(0, globals_1.it)('should maintain existing parameter functionality', () => {
|
|
607
|
+
const timeline = contextRepo.getTimelineData({
|
|
608
|
+
sessionId: testSessionId,
|
|
609
|
+
groupBy: 'day',
|
|
610
|
+
categories: ['task'],
|
|
611
|
+
includeItems: true,
|
|
612
|
+
itemsPerPeriod: 2,
|
|
613
|
+
});
|
|
614
|
+
// Should filter by category and limit items
|
|
615
|
+
timeline.forEach((period) => {
|
|
616
|
+
if (period.items && period.items.length > 0) {
|
|
617
|
+
(0, globals_1.expect)(period.items.every((item) => item.category === 'task')).toBe(true);
|
|
618
|
+
(0, globals_1.expect)(period.items.length).toBeLessThanOrEqual(2);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
(0, globals_1.it)('should handle undefined new parameters gracefully', () => {
|
|
623
|
+
const timeline = contextRepo.getTimelineData({
|
|
624
|
+
sessionId: testSessionId,
|
|
625
|
+
groupBy: 'day',
|
|
626
|
+
minItemsPerPeriod: undefined,
|
|
627
|
+
showEmpty: undefined,
|
|
628
|
+
});
|
|
629
|
+
// Should behave as if parameters weren't provided
|
|
630
|
+
(0, globals_1.expect)(timeline.length).toBeGreaterThan(0);
|
|
631
|
+
(0, globals_1.expect)(timeline.every((period) => period.count > 0)).toBe(true);
|
|
632
|
+
});
|
|
633
|
+
(0, globals_1.it)('should maintain response format compatibility', () => {
|
|
634
|
+
const timeline = contextRepo.getTimelineData({
|
|
635
|
+
sessionId: testSessionId,
|
|
636
|
+
groupBy: 'day',
|
|
637
|
+
includeItems: true,
|
|
638
|
+
});
|
|
639
|
+
// Verify expected structure
|
|
640
|
+
timeline.forEach((period) => {
|
|
641
|
+
(0, globals_1.expect)(period).toHaveProperty('period');
|
|
642
|
+
(0, globals_1.expect)(period).toHaveProperty('count');
|
|
643
|
+
(0, globals_1.expect)(period).toHaveProperty('items');
|
|
644
|
+
(0, globals_1.expect)(typeof period.period).toBe('string');
|
|
645
|
+
(0, globals_1.expect)(typeof period.count).toBe('number');
|
|
646
|
+
(0, globals_1.expect)(Array.isArray(period.items)).toBe(true);
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
(0, globals_1.describe)('Handler Response Format Tests', () => {
|
|
651
|
+
(0, globals_1.beforeEach)(() => {
|
|
652
|
+
createTestDataWithTimeline();
|
|
653
|
+
});
|
|
654
|
+
(0, globals_1.it)('should format timeline response correctly', () => {
|
|
655
|
+
const timeline = contextRepo.getTimelineData({
|
|
656
|
+
sessionId: testSessionId,
|
|
657
|
+
groupBy: 'day',
|
|
658
|
+
minItemsPerPeriod: 2,
|
|
659
|
+
});
|
|
660
|
+
// Simulate handler formatting
|
|
661
|
+
const formattedPeriods = timeline
|
|
662
|
+
.map((p) => `${p.period}: ${p.count} items${p.hasMore ? ` (showing ${p.items?.length || 0} of ${p.totalCount})` : ''}`)
|
|
663
|
+
.join('\n');
|
|
664
|
+
const handlerResponse = {
|
|
665
|
+
content: [
|
|
666
|
+
{
|
|
667
|
+
type: 'text',
|
|
668
|
+
text: `Timeline (${timeline.length} periods):\n\n${formattedPeriods}`,
|
|
669
|
+
},
|
|
670
|
+
],
|
|
671
|
+
};
|
|
672
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('Timeline');
|
|
673
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('periods');
|
|
674
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('items');
|
|
675
|
+
});
|
|
676
|
+
(0, globals_1.it)('should include empty periods in response when showEmpty is true', () => {
|
|
677
|
+
// Use UTC-based dates for consistent behavior across timezones
|
|
678
|
+
const endDate = new Date('2025-06-20T12:00:00.000Z');
|
|
679
|
+
const startDate = new Date(Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate() - 3, 12, 0, 0));
|
|
680
|
+
const timeline = contextRepo.getTimelineData({
|
|
681
|
+
sessionId: testSessionId,
|
|
682
|
+
startDate: startDate.toISOString(),
|
|
683
|
+
endDate: endDate.toISOString(),
|
|
684
|
+
groupBy: 'day',
|
|
685
|
+
showEmpty: true,
|
|
686
|
+
});
|
|
687
|
+
// Handler should indicate empty periods
|
|
688
|
+
const formattedPeriods = timeline
|
|
689
|
+
.map((p) => `${p.period}: ${p.count === 0 ? 'No items' : `${p.count} items`}`)
|
|
690
|
+
.join('\n');
|
|
691
|
+
(0, globals_1.expect)(formattedPeriods).toContain('No items');
|
|
692
|
+
});
|
|
693
|
+
(0, globals_1.it)('should format response with journal entries integration', () => {
|
|
694
|
+
// Add journal entries
|
|
695
|
+
db.prepare(`
|
|
696
|
+
INSERT INTO journal_entries (id, session_id, entry, created_at)
|
|
697
|
+
VALUES (?, ?, ?, ?)
|
|
698
|
+
`).run((0, uuid_1.v4)(), testSessionId, 'Test journal entry', new Date('2025-06-20T12:00:00.000Z').toISOString());
|
|
699
|
+
const timeline = contextRepo.getTimelineData({
|
|
700
|
+
sessionId: testSessionId,
|
|
701
|
+
groupBy: 'day',
|
|
702
|
+
includeItems: true,
|
|
703
|
+
});
|
|
704
|
+
// Handler would merge context items and journal entries
|
|
705
|
+
const handlerResponse = {
|
|
706
|
+
content: [
|
|
707
|
+
{
|
|
708
|
+
type: 'text',
|
|
709
|
+
text: `Timeline with ${timeline.length} periods and journal entries`,
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
};
|
|
713
|
+
(0, globals_1.expect)(handlerResponse.content[0].text).toContain('journal entries');
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
});
|