mcp-memory-keeper 0.10.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/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +1051 -0
- package/bin/mcp-memory-keeper +52 -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 +938 -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/cross-session-sharing.test.js +302 -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 +407 -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/git-integration.test.js +237 -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/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 +283 -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 +307 -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/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 +188 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -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 +4310 -0
- package/dist/index.phase1.backup.js +410 -0
- package/dist/index.phase2.backup.js +704 -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 +1873 -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/server.js +384 -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 +731 -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/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/package.json +84 -0
|
@@ -0,0 +1,528 @@
|
|
|
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 database_1 = require("../../utils/database");
|
|
37
|
+
const migrations_1 = require("../../utils/migrations");
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
describe('Database Migration Integration Tests', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let migrationManager;
|
|
44
|
+
let tempDbPath;
|
|
45
|
+
let db;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-migrations-${Date.now()}.db`);
|
|
48
|
+
dbManager = new database_1.DatabaseManager({
|
|
49
|
+
filename: tempDbPath,
|
|
50
|
+
maxSize: 10 * 1024 * 1024,
|
|
51
|
+
walMode: true,
|
|
52
|
+
});
|
|
53
|
+
db = dbManager.getDatabase();
|
|
54
|
+
migrationManager = new migrations_1.MigrationManager(dbManager);
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
dbManager.close();
|
|
58
|
+
try {
|
|
59
|
+
fs.unlinkSync(tempDbPath);
|
|
60
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
61
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
62
|
+
}
|
|
63
|
+
catch (_e) {
|
|
64
|
+
// Ignore
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
describe('Migration Management', () => {
|
|
68
|
+
it('should create and retrieve migrations', () => {
|
|
69
|
+
const migration = {
|
|
70
|
+
version: '1.0.0',
|
|
71
|
+
name: 'Add test table',
|
|
72
|
+
description: 'Creates a test table for testing',
|
|
73
|
+
up: 'CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);',
|
|
74
|
+
down: 'DROP TABLE test_table;',
|
|
75
|
+
requiresBackup: true,
|
|
76
|
+
};
|
|
77
|
+
const migrationId = migrationManager.createMigration(migration);
|
|
78
|
+
expect(migrationId).toBeDefined();
|
|
79
|
+
expect(typeof migrationId).toBe('string');
|
|
80
|
+
const retrieved = migrationManager.getMigration(migration.version);
|
|
81
|
+
expect(retrieved).toBeDefined();
|
|
82
|
+
expect(retrieved.name).toBe(migration.name);
|
|
83
|
+
expect(retrieved.version).toBe(migration.version);
|
|
84
|
+
expect(retrieved.up).toBe(migration.up);
|
|
85
|
+
expect(retrieved.down).toBe(migration.down);
|
|
86
|
+
expect(retrieved.requiresBackup).toBe(migration.requiresBackup);
|
|
87
|
+
});
|
|
88
|
+
it('should list all migrations', () => {
|
|
89
|
+
const migrations = [
|
|
90
|
+
{ version: '1.0.0', name: 'Migration 1', up: 'SELECT 1;' },
|
|
91
|
+
{ version: '1.1.0', name: 'Migration 2', up: 'SELECT 2;' },
|
|
92
|
+
{ version: '1.2.0', name: 'Migration 3', up: 'SELECT 3;' },
|
|
93
|
+
];
|
|
94
|
+
const migrationIds = migrations.map(m => migrationManager.createMigration(m));
|
|
95
|
+
const allMigrations = migrationManager.listMigrations();
|
|
96
|
+
expect(allMigrations.length).toBeGreaterThanOrEqual(3);
|
|
97
|
+
const createdMigrations = allMigrations.filter(m => migrationIds.includes(m.id));
|
|
98
|
+
expect(createdMigrations.length).toBe(3);
|
|
99
|
+
});
|
|
100
|
+
it('should filter migrations by applied status', () => {
|
|
101
|
+
const migrations = [
|
|
102
|
+
{ version: '2.0.0', name: 'Applied Migration', up: 'SELECT 1;' },
|
|
103
|
+
{ version: '2.1.0', name: 'Pending Migration', up: 'SELECT 2;' },
|
|
104
|
+
];
|
|
105
|
+
migrations.forEach(m => migrationManager.createMigration(m));
|
|
106
|
+
// Apply one migration
|
|
107
|
+
const appliedMigration = migrationManager.getMigration('2.0.0');
|
|
108
|
+
db.prepare('UPDATE migrations SET applied_at = CURRENT_TIMESTAMP WHERE id = ?').run(appliedMigration.id);
|
|
109
|
+
const appliedMigrations = migrationManager.listMigrations({ applied: true });
|
|
110
|
+
const pendingMigrations = migrationManager.listMigrations({ pending: true });
|
|
111
|
+
expect(appliedMigrations.some(m => m.version === '2.0.0')).toBe(true);
|
|
112
|
+
expect(pendingMigrations.some(m => m.version === '2.1.0')).toBe(true);
|
|
113
|
+
expect(pendingMigrations.some(m => m.version === '2.0.0')).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
it('should enforce unique versions', () => {
|
|
116
|
+
const migration1 = { version: '3.0.0', name: 'Migration 1', up: 'SELECT 1;' };
|
|
117
|
+
const migration2 = { version: '3.0.0', name: 'Migration 2', up: 'SELECT 2;' };
|
|
118
|
+
migrationManager.createMigration(migration1);
|
|
119
|
+
expect(() => migrationManager.createMigration(migration2)).toThrow();
|
|
120
|
+
});
|
|
121
|
+
it('should calculate checksums for migrations', () => {
|
|
122
|
+
const migration = {
|
|
123
|
+
version: '4.0.0',
|
|
124
|
+
name: 'Checksum Test',
|
|
125
|
+
up: 'CREATE TABLE checksum_test (id INTEGER);',
|
|
126
|
+
down: 'DROP TABLE checksum_test;',
|
|
127
|
+
};
|
|
128
|
+
migrationManager.createMigration(migration);
|
|
129
|
+
const retrieved = migrationManager.getMigration(migration.version);
|
|
130
|
+
expect(retrieved.checksum).toBeDefined();
|
|
131
|
+
expect(retrieved.checksum).toHaveLength(16); // SHA-256 substring
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('Migration Status', () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
// Create test migrations
|
|
137
|
+
const migrations = [
|
|
138
|
+
{ version: '5.0.0', name: 'Migration 1', up: 'SELECT 1;' },
|
|
139
|
+
{ version: '5.1.0', name: 'Migration 2', up: 'SELECT 2;', requiresBackup: true },
|
|
140
|
+
{ version: '5.2.0', name: 'Migration 3', up: 'SELECT 3;' },
|
|
141
|
+
];
|
|
142
|
+
migrations.forEach(m => migrationManager.createMigration(m));
|
|
143
|
+
// Apply first migration
|
|
144
|
+
const appliedMigration = migrationManager.getMigration('5.0.0');
|
|
145
|
+
db.prepare('UPDATE migrations SET applied_at = CURRENT_TIMESTAMP WHERE id = ?').run(appliedMigration.id);
|
|
146
|
+
});
|
|
147
|
+
it('should provide comprehensive status', () => {
|
|
148
|
+
const status = migrationManager.getStatus();
|
|
149
|
+
expect(status.totalMigrations).toBeGreaterThanOrEqual(3);
|
|
150
|
+
expect(status.appliedMigrations).toBeGreaterThanOrEqual(1);
|
|
151
|
+
expect(status.pendingMigrations).toBeGreaterThanOrEqual(2);
|
|
152
|
+
expect(status.pending.length).toBeGreaterThanOrEqual(2);
|
|
153
|
+
expect(status.applied.length).toBeGreaterThanOrEqual(1);
|
|
154
|
+
expect(status.lastMigration).toBeDefined();
|
|
155
|
+
expect(status.lastMigration.version).toBe('0.4.0');
|
|
156
|
+
});
|
|
157
|
+
it('should identify migrations requiring backups', () => {
|
|
158
|
+
const status = migrationManager.getStatus();
|
|
159
|
+
const backupRequired = status.pending.find(m => m.requiresBackup);
|
|
160
|
+
expect(backupRequired).toBeDefined();
|
|
161
|
+
expect(backupRequired.version).toBe('5.1.0');
|
|
162
|
+
});
|
|
163
|
+
it('should handle empty migration state', () => {
|
|
164
|
+
// Clear all migrations
|
|
165
|
+
db.prepare('DELETE FROM migrations').run();
|
|
166
|
+
const status = migrationManager.getStatus();
|
|
167
|
+
expect(status.totalMigrations).toBe(0);
|
|
168
|
+
expect(status.appliedMigrations).toBe(0);
|
|
169
|
+
expect(status.pendingMigrations).toBe(0);
|
|
170
|
+
expect(status.currentVersion).toBe('0.0.0');
|
|
171
|
+
expect(status.lastMigration).toBeUndefined();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe('Migration Execution', () => {
|
|
175
|
+
beforeEach(() => {
|
|
176
|
+
// Create test table for migration testing
|
|
177
|
+
db.exec(`
|
|
178
|
+
CREATE TABLE IF NOT EXISTS migration_test (
|
|
179
|
+
id INTEGER PRIMARY KEY,
|
|
180
|
+
name TEXT
|
|
181
|
+
);
|
|
182
|
+
`);
|
|
183
|
+
});
|
|
184
|
+
it('should apply migration successfully', async () => {
|
|
185
|
+
const migration = {
|
|
186
|
+
version: '6.0.0',
|
|
187
|
+
name: 'Add column test',
|
|
188
|
+
up: 'ALTER TABLE migration_test ADD COLUMN email TEXT;',
|
|
189
|
+
down: 'ALTER TABLE migration_test DROP COLUMN email;',
|
|
190
|
+
};
|
|
191
|
+
migrationManager.createMigration(migration);
|
|
192
|
+
const result = await migrationManager.applyMigration('6.0.0', { dryRun: false });
|
|
193
|
+
expect(result.success).toBe(true);
|
|
194
|
+
expect(result.errors.length).toBe(0);
|
|
195
|
+
expect(result.executionTime).toBeGreaterThanOrEqual(0);
|
|
196
|
+
// Verify migration was marked as applied
|
|
197
|
+
const appliedMigration = migrationManager.getMigration('6.0.0');
|
|
198
|
+
expect(appliedMigration.appliedAt).toBeDefined();
|
|
199
|
+
// Verify schema change
|
|
200
|
+
const columns = db.prepare('PRAGMA table_info(migration_test)').all();
|
|
201
|
+
expect(columns.some((col) => col.name === 'email')).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
it('should perform dry run without changes', async () => {
|
|
204
|
+
const migration = {
|
|
205
|
+
version: '6.1.0',
|
|
206
|
+
name: 'Dry run test',
|
|
207
|
+
up: 'ALTER TABLE migration_test ADD COLUMN phone TEXT;',
|
|
208
|
+
};
|
|
209
|
+
migrationManager.createMigration(migration);
|
|
210
|
+
const result = await migrationManager.applyMigration('6.1.0', { dryRun: true });
|
|
211
|
+
expect(result.success).toBe(true);
|
|
212
|
+
expect(result.warnings).toContain('Dry run - no changes applied');
|
|
213
|
+
// Verify migration was NOT marked as applied
|
|
214
|
+
const appliedMigration = migrationManager.getMigration('6.1.0');
|
|
215
|
+
expect(appliedMigration.appliedAt).toBeNull();
|
|
216
|
+
// Verify no schema change
|
|
217
|
+
const columns = db.prepare('PRAGMA table_info(migration_test)').all();
|
|
218
|
+
expect(columns.some((col) => col.name === 'phone')).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
it('should rollback migration successfully', async () => {
|
|
221
|
+
const migration = {
|
|
222
|
+
version: '6.2.0',
|
|
223
|
+
name: 'Rollback test',
|
|
224
|
+
up: 'ALTER TABLE migration_test ADD COLUMN temp_column TEXT;',
|
|
225
|
+
down: 'ALTER TABLE migration_test DROP COLUMN temp_column;',
|
|
226
|
+
};
|
|
227
|
+
migrationManager.createMigration(migration);
|
|
228
|
+
// Apply migration first
|
|
229
|
+
await migrationManager.applyMigration('6.2.0', { dryRun: false });
|
|
230
|
+
// Verify column was added
|
|
231
|
+
let columns = db.prepare('PRAGMA table_info(migration_test)').all();
|
|
232
|
+
expect(columns.some((col) => col.name === 'temp_column')).toBe(true);
|
|
233
|
+
// Rollback migration
|
|
234
|
+
const result = await migrationManager.rollbackMigration('6.2.0', { dryRun: false });
|
|
235
|
+
expect(result.success).toBe(true);
|
|
236
|
+
expect(result.errors.length).toBe(0);
|
|
237
|
+
// Verify migration was marked as rolled back
|
|
238
|
+
const rolledBackMigration = migrationManager.getMigration('6.2.0');
|
|
239
|
+
expect(rolledBackMigration.appliedAt).toBeNull();
|
|
240
|
+
expect(rolledBackMigration.rollbackAt).toBeDefined();
|
|
241
|
+
// Verify column was removed
|
|
242
|
+
columns = db.prepare('PRAGMA table_info(migration_test)').all();
|
|
243
|
+
expect(columns.some((col) => col.name === 'temp_column')).toBe(false);
|
|
244
|
+
});
|
|
245
|
+
it('should handle migration dependencies', async () => {
|
|
246
|
+
const migrations = [
|
|
247
|
+
{ version: '7.0.0', name: 'Base migration', up: 'SELECT 1;' },
|
|
248
|
+
{
|
|
249
|
+
version: '7.1.0',
|
|
250
|
+
name: 'Dependent migration',
|
|
251
|
+
up: 'SELECT 2;',
|
|
252
|
+
dependencies: ['7.0.0'],
|
|
253
|
+
},
|
|
254
|
+
];
|
|
255
|
+
migrations.forEach(m => migrationManager.createMigration(m));
|
|
256
|
+
// Try to apply dependent migration without dependency
|
|
257
|
+
const failResult = await migrationManager.applyMigration('7.1.0', { dryRun: false });
|
|
258
|
+
expect(failResult.success).toBe(false);
|
|
259
|
+
expect(failResult.errors).toContain('Dependency not satisfied: 7.0.0');
|
|
260
|
+
// Apply base migration first
|
|
261
|
+
await migrationManager.applyMigration('7.0.0', { dryRun: false });
|
|
262
|
+
// Now dependent migration should work
|
|
263
|
+
const successResult = await migrationManager.applyMigration('7.1.0', { dryRun: false });
|
|
264
|
+
expect(successResult.success).toBe(true);
|
|
265
|
+
});
|
|
266
|
+
it('should validate SQL before execution', async () => {
|
|
267
|
+
const migration = {
|
|
268
|
+
version: '7.2.0',
|
|
269
|
+
name: 'Invalid SQL',
|
|
270
|
+
up: '', // Empty SQL
|
|
271
|
+
};
|
|
272
|
+
migrationManager.createMigration(migration);
|
|
273
|
+
const result = await migrationManager.applyMigration('7.2.0', { dryRun: false });
|
|
274
|
+
expect(result.success).toBe(false);
|
|
275
|
+
expect(result.errors).toContain('Empty SQL statement');
|
|
276
|
+
});
|
|
277
|
+
it('should detect dangerous SQL operations', async () => {
|
|
278
|
+
const migration = {
|
|
279
|
+
version: '7.3.0',
|
|
280
|
+
name: 'Dangerous SQL',
|
|
281
|
+
up: 'DELETE FROM migration_test;', // DELETE without WHERE
|
|
282
|
+
};
|
|
283
|
+
migrationManager.createMigration(migration);
|
|
284
|
+
const result = await migrationManager.applyMigration('7.3.0', { dryRun: false });
|
|
285
|
+
expect(result.success).toBe(false);
|
|
286
|
+
expect(result.errors).toContain('Potentially dangerous SQL detected');
|
|
287
|
+
});
|
|
288
|
+
it('should handle transaction rollback on error', async () => {
|
|
289
|
+
const migration = {
|
|
290
|
+
version: '7.4.0',
|
|
291
|
+
name: 'Error migration',
|
|
292
|
+
up: 'ALTER TABLE non_existent_table ADD COLUMN test TEXT;', // Should fail
|
|
293
|
+
};
|
|
294
|
+
migrationManager.createMigration(migration);
|
|
295
|
+
const result = await migrationManager.applyMigration('7.4.0', { dryRun: false });
|
|
296
|
+
expect(result.success).toBe(false);
|
|
297
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
298
|
+
// Verify migration was NOT marked as applied
|
|
299
|
+
const failedMigration = migrationManager.getMigration('7.4.0');
|
|
300
|
+
expect(failedMigration.appliedAt).toBeNull();
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
describe('Batch Operations', () => {
|
|
304
|
+
beforeEach(() => {
|
|
305
|
+
// Create multiple pending migrations
|
|
306
|
+
const migrations = [
|
|
307
|
+
{ version: '8.0.0', name: 'Migration 1', up: 'SELECT 1;' },
|
|
308
|
+
{ version: '8.1.0', name: 'Migration 2', up: 'SELECT 2;' },
|
|
309
|
+
{ version: '8.2.0', name: 'Migration 3', up: 'SELECT 3;' },
|
|
310
|
+
];
|
|
311
|
+
migrations.forEach(m => migrationManager.createMigration(m));
|
|
312
|
+
});
|
|
313
|
+
it('should apply all pending migrations', async () => {
|
|
314
|
+
const results = await migrationManager.applyAllPending({ dryRun: false });
|
|
315
|
+
expect(results.length).toBe(3);
|
|
316
|
+
expect(results.every(r => r.success)).toBe(true);
|
|
317
|
+
// Verify all migrations were applied
|
|
318
|
+
const status = migrationManager.getStatus();
|
|
319
|
+
expect(status.pendingMigrations).toBe(0);
|
|
320
|
+
expect(status.appliedMigrations).toBeGreaterThanOrEqual(3);
|
|
321
|
+
});
|
|
322
|
+
it('should stop on error when configured', async () => {
|
|
323
|
+
// Add a migration that will fail
|
|
324
|
+
const badMigration = {
|
|
325
|
+
version: '8.3.0',
|
|
326
|
+
name: 'Bad Migration',
|
|
327
|
+
up: 'INVALID SQL SYNTAX;',
|
|
328
|
+
};
|
|
329
|
+
migrationManager.createMigration(badMigration);
|
|
330
|
+
const results = await migrationManager.applyAllPending({
|
|
331
|
+
dryRun: false,
|
|
332
|
+
stopOnError: true,
|
|
333
|
+
});
|
|
334
|
+
// Should have tried 4 migrations but stopped at the invalid one
|
|
335
|
+
expect(results.length).toBeLessThanOrEqual(4);
|
|
336
|
+
expect(results.some(r => !r.success)).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
it('should continue on error when configured', async () => {
|
|
339
|
+
// Add a migration that will fail
|
|
340
|
+
const badMigration = {
|
|
341
|
+
version: '8.4.0',
|
|
342
|
+
name: 'Bad Migration',
|
|
343
|
+
up: 'INVALID SQL SYNTAX;',
|
|
344
|
+
};
|
|
345
|
+
migrationManager.createMigration(badMigration);
|
|
346
|
+
const results = await migrationManager.applyAllPending({
|
|
347
|
+
dryRun: false,
|
|
348
|
+
stopOnError: false,
|
|
349
|
+
});
|
|
350
|
+
// Should have tried all 4 migrations
|
|
351
|
+
expect(results.length).toBe(4);
|
|
352
|
+
expect(results.some(r => r.success)).toBe(true);
|
|
353
|
+
expect(results.some(r => !r.success)).toBe(true);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
describe('Logging and Audit', () => {
|
|
357
|
+
it('should log migration execution', async () => {
|
|
358
|
+
const migration = {
|
|
359
|
+
version: '9.0.0',
|
|
360
|
+
name: 'Logging test',
|
|
361
|
+
up: 'SELECT 1;',
|
|
362
|
+
};
|
|
363
|
+
migrationManager.createMigration(migration);
|
|
364
|
+
await migrationManager.applyMigration('9.0.0', { dryRun: false });
|
|
365
|
+
const logs = migrationManager.getMigrationLog('9.0.0');
|
|
366
|
+
expect(logs.length).toBeGreaterThan(0);
|
|
367
|
+
const logEntry = logs[0];
|
|
368
|
+
expect(logEntry.version).toBe('9.0.0');
|
|
369
|
+
expect(logEntry.action).toBe('apply');
|
|
370
|
+
expect(logEntry.success).toBe(1); // SQLite boolean
|
|
371
|
+
expect(logEntry.execution_time).toBeGreaterThanOrEqual(0);
|
|
372
|
+
});
|
|
373
|
+
it('should log rollback operations', async () => {
|
|
374
|
+
const migration = {
|
|
375
|
+
version: '9.1.0',
|
|
376
|
+
name: 'Rollback logging test',
|
|
377
|
+
up: 'SELECT 1;',
|
|
378
|
+
down: 'SELECT 0;',
|
|
379
|
+
};
|
|
380
|
+
migrationManager.createMigration(migration);
|
|
381
|
+
await migrationManager.applyMigration('9.1.0', { dryRun: false });
|
|
382
|
+
await migrationManager.rollbackMigration('9.1.0', { dryRun: false });
|
|
383
|
+
const logs = migrationManager.getMigrationLog('9.1.0');
|
|
384
|
+
expect(logs.length).toBe(2);
|
|
385
|
+
const applyLog = logs.find(l => l.action === 'apply');
|
|
386
|
+
const rollbackLog = logs.find(l => l.action === 'rollback');
|
|
387
|
+
expect(applyLog).toBeDefined();
|
|
388
|
+
expect(rollbackLog).toBeDefined();
|
|
389
|
+
});
|
|
390
|
+
it('should limit log results', () => {
|
|
391
|
+
const logs = migrationManager.getMigrationLog(undefined, 5);
|
|
392
|
+
expect(logs.length).toBeLessThanOrEqual(5);
|
|
393
|
+
});
|
|
394
|
+
it('should log errors properly', async () => {
|
|
395
|
+
const migration = {
|
|
396
|
+
version: '9.2.0',
|
|
397
|
+
name: 'Error logging test',
|
|
398
|
+
up: 'INVALID SQL;',
|
|
399
|
+
};
|
|
400
|
+
migrationManager.createMigration(migration);
|
|
401
|
+
const result = await migrationManager.applyMigration('9.2.0', { dryRun: false });
|
|
402
|
+
expect(result.success).toBe(false);
|
|
403
|
+
const logs = migrationManager.getMigrationLog('9.2.0');
|
|
404
|
+
const errorLog = logs[0];
|
|
405
|
+
expect(errorLog.success).toBe(0); // SQLite boolean
|
|
406
|
+
expect(errorLog.errors).toBeDefined();
|
|
407
|
+
const errors = JSON.parse(errorLog.errors);
|
|
408
|
+
expect(errors.length).toBeGreaterThan(0);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
describe('Default Migrations', () => {
|
|
412
|
+
it('should provide default migrations', () => {
|
|
413
|
+
const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
|
|
414
|
+
expect(defaultMigrations.length).toBeGreaterThan(0);
|
|
415
|
+
expect(defaultMigrations[0]).toHaveProperty('version');
|
|
416
|
+
expect(defaultMigrations[0]).toHaveProperty('name');
|
|
417
|
+
expect(defaultMigrations[0]).toHaveProperty('up');
|
|
418
|
+
});
|
|
419
|
+
it('should create default migrations without errors', () => {
|
|
420
|
+
const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
|
|
421
|
+
// Get initial migration count
|
|
422
|
+
const initialCount = migrationManager.listMigrations().length;
|
|
423
|
+
for (const migration of defaultMigrations) {
|
|
424
|
+
expect(() => migrationManager.createMigration(migration)).not.toThrow();
|
|
425
|
+
}
|
|
426
|
+
const createdMigrations = migrationManager.listMigrations();
|
|
427
|
+
expect(createdMigrations.length).toBe(initialCount + defaultMigrations.length);
|
|
428
|
+
});
|
|
429
|
+
it('should have proper dependency chains', () => {
|
|
430
|
+
const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
|
|
431
|
+
// Check that dependencies exist
|
|
432
|
+
const versions = defaultMigrations.map((m) => m.version);
|
|
433
|
+
for (const migration of defaultMigrations) {
|
|
434
|
+
if (migration.dependencies) {
|
|
435
|
+
for (const dep of migration.dependencies) {
|
|
436
|
+
expect(versions).toContain(dep);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
it('should have rollback SQL for important migrations', () => {
|
|
442
|
+
const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
|
|
443
|
+
// Most default migrations should have rollback capability
|
|
444
|
+
const migrationsWithRollback = defaultMigrations.filter((m) => m.down);
|
|
445
|
+
expect(migrationsWithRollback.length).toBeGreaterThan(0);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
describe('Error Handling', () => {
|
|
449
|
+
it('should handle non-existent migration application', async () => {
|
|
450
|
+
await expect(migrationManager.applyMigration('non-existent')).rejects.toThrow('Migration not found');
|
|
451
|
+
});
|
|
452
|
+
it('should handle non-existent migration rollback', async () => {
|
|
453
|
+
await expect(migrationManager.rollbackMigration('non-existent')).rejects.toThrow('Migration not found');
|
|
454
|
+
});
|
|
455
|
+
it('should handle double application', async () => {
|
|
456
|
+
const migration = {
|
|
457
|
+
version: '10.0.0',
|
|
458
|
+
name: 'Double apply test',
|
|
459
|
+
up: 'SELECT 1;',
|
|
460
|
+
};
|
|
461
|
+
migrationManager.createMigration(migration);
|
|
462
|
+
await migrationManager.applyMigration('10.0.0', { dryRun: false });
|
|
463
|
+
await expect(migrationManager.applyMigration('10.0.0', { dryRun: false })).rejects.toThrow('already applied');
|
|
464
|
+
});
|
|
465
|
+
it('should handle rollback without down SQL', async () => {
|
|
466
|
+
const migration = {
|
|
467
|
+
version: '10.1.0',
|
|
468
|
+
name: 'No rollback test',
|
|
469
|
+
up: 'SELECT 1;',
|
|
470
|
+
// No down SQL
|
|
471
|
+
};
|
|
472
|
+
migrationManager.createMigration(migration);
|
|
473
|
+
await migrationManager.applyMigration('10.1.0', { dryRun: false });
|
|
474
|
+
await expect(migrationManager.rollbackMigration('10.1.0', { dryRun: false })).rejects.toThrow('no rollback SQL');
|
|
475
|
+
});
|
|
476
|
+
it('should handle rollback of non-applied migration', async () => {
|
|
477
|
+
const migration = {
|
|
478
|
+
version: '10.2.0',
|
|
479
|
+
name: 'Not applied test',
|
|
480
|
+
up: 'SELECT 1;',
|
|
481
|
+
down: 'SELECT 0;',
|
|
482
|
+
};
|
|
483
|
+
migrationManager.createMigration(migration);
|
|
484
|
+
await expect(migrationManager.rollbackMigration('10.2.0', { dryRun: false })).rejects.toThrow('not applied');
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
describe('Edge Cases', () => {
|
|
488
|
+
it('should handle migrations with no description', () => {
|
|
489
|
+
const migration = {
|
|
490
|
+
version: '11.0.0',
|
|
491
|
+
name: 'No description',
|
|
492
|
+
up: 'SELECT 1;',
|
|
493
|
+
};
|
|
494
|
+
expect(() => migrationManager.createMigration(migration)).not.toThrow();
|
|
495
|
+
const retrieved = migrationManager.getMigration('11.0.0');
|
|
496
|
+
expect(retrieved.description).toBeNull();
|
|
497
|
+
});
|
|
498
|
+
it('should handle migrations with no dependencies', () => {
|
|
499
|
+
const migration = {
|
|
500
|
+
version: '11.1.0',
|
|
501
|
+
name: 'No dependencies',
|
|
502
|
+
up: 'SELECT 1;',
|
|
503
|
+
};
|
|
504
|
+
migrationManager.createMigration(migration);
|
|
505
|
+
// Should apply without dependency check
|
|
506
|
+
expect(async () => {
|
|
507
|
+
await migrationManager.applyMigration('11.1.0', { dryRun: false });
|
|
508
|
+
}).not.toThrow();
|
|
509
|
+
});
|
|
510
|
+
it('should handle empty migration list for batch operations', async () => {
|
|
511
|
+
// Clear all migrations
|
|
512
|
+
db.prepare('DELETE FROM migrations').run();
|
|
513
|
+
const results = await migrationManager.applyAllPending({ dryRun: false });
|
|
514
|
+
expect(results.length).toBe(0);
|
|
515
|
+
});
|
|
516
|
+
it('should handle very long SQL statements', () => {
|
|
517
|
+
const longSQL = 'SELECT ' + '1,'.repeat(1000) + '1;';
|
|
518
|
+
const migration = {
|
|
519
|
+
version: '11.2.0',
|
|
520
|
+
name: 'Long SQL',
|
|
521
|
+
up: longSQL,
|
|
522
|
+
};
|
|
523
|
+
expect(() => migrationManager.createMigration(migration)).not.toThrow();
|
|
524
|
+
const retrieved = migrationManager.getMigration('11.2.0');
|
|
525
|
+
expect(retrieved.up).toBe(longSQL);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
});
|