local-memory-mcp 1.0.2
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 +46 -0
- package/README.md +256 -0
- package/bin/local-memory +15 -0
- package/bin/local-memory-macos-arm +0 -0
- package/bin/local-memory-macos-intel +0 -0
- package/bin/local-memory-windows.exe +0 -0
- package/index.js +120 -0
- package/package.json +79 -0
- package/scripts/install.js +300 -0
- package/scripts/test.js +193 -0
- package/scripts/uninstall.js +106 -0
- package/test/test-integration.js +423 -0
- package/test/test-npm-package.js +408 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Local Memory Server NPM Package - Integration Test Suite
|
|
5
|
+
*
|
|
6
|
+
* This performs full integration testing including:
|
|
7
|
+
* - Actual binary downloads
|
|
8
|
+
* - License validation integration
|
|
9
|
+
* - MCP server functionality
|
|
10
|
+
* - Cross-platform compatibility
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
// Test configuration
|
|
19
|
+
const INTEGRATION_CONFIG = {
|
|
20
|
+
timeout: 60000, // 1 minute for downloads
|
|
21
|
+
verbose: process.argv.includes('--verbose') || process.env.VERBOSE === 'true',
|
|
22
|
+
cleanup: !process.argv.includes('--no-cleanup'),
|
|
23
|
+
testDir: path.join(os.tmpdir(), `local-memory-integration-test-${Date.now()}`),
|
|
24
|
+
skipDownload: process.argv.includes('--skip-download'),
|
|
25
|
+
testLicense: process.env.TEST_LICENSE_KEY || 'LM-A2CD-E4GH-23KL-MN4P-QR2T' // Test key for validation
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Integration Test Runner
|
|
30
|
+
*/
|
|
31
|
+
class IntegrationTestRunner {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.passed = 0;
|
|
34
|
+
this.failed = 0;
|
|
35
|
+
this.skipped = 0;
|
|
36
|
+
this.binaryPath = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
log(message, level = 'info') {
|
|
40
|
+
const prefix = {
|
|
41
|
+
info: '๐',
|
|
42
|
+
success: 'โ
',
|
|
43
|
+
error: 'โ',
|
|
44
|
+
warning: 'โ ๏ธ',
|
|
45
|
+
debug: '๐',
|
|
46
|
+
download: '๐ฅ'
|
|
47
|
+
}[level] || '๐';
|
|
48
|
+
|
|
49
|
+
console.log(`${prefix} ${message}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async runCommand(command, args = [], options = {}) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const proc = spawn(command, args, {
|
|
55
|
+
stdio: INTEGRATION_CONFIG.verbose ? 'inherit' : 'pipe',
|
|
56
|
+
timeout: options.timeout || INTEGRATION_CONFIG.timeout,
|
|
57
|
+
cwd: options.cwd || process.cwd(),
|
|
58
|
+
env: { ...process.env, ...options.env }
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
let stdout = '';
|
|
62
|
+
let stderr = '';
|
|
63
|
+
|
|
64
|
+
if (!INTEGRATION_CONFIG.verbose) {
|
|
65
|
+
proc.stdout?.on('data', data => stdout += data.toString());
|
|
66
|
+
proc.stderr?.on('data', data => stderr += data.toString());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
proc.on('close', code => resolve({ code, stdout, stderr }));
|
|
70
|
+
proc.on('error', reject);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async test(name, testFn, options = {}) {
|
|
75
|
+
this.log(`Testing: ${name}`);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (options.skip) {
|
|
79
|
+
this.skipped++;
|
|
80
|
+
this.log(`SKIP: ${name}`, 'warning');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await testFn();
|
|
85
|
+
this.passed++;
|
|
86
|
+
this.log(`PASS: ${name}`, 'success');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.failed++;
|
|
89
|
+
this.log(`FAIL: ${name} - ${error.message}`, 'error');
|
|
90
|
+
|
|
91
|
+
if (INTEGRATION_CONFIG.verbose) {
|
|
92
|
+
console.error(error.stack);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (options.critical) {
|
|
96
|
+
throw new Error(`Critical integration test failed: ${name}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
summary() {
|
|
102
|
+
const total = this.passed + this.failed + this.skipped;
|
|
103
|
+
this.log(`\nIntegration Test Summary: ${this.passed}/${total} passed, ${this.failed} failed, ${this.skipped} skipped`);
|
|
104
|
+
|
|
105
|
+
if (this.failed > 0) {
|
|
106
|
+
this.log('Some integration tests failed!', 'error');
|
|
107
|
+
return false;
|
|
108
|
+
} else {
|
|
109
|
+
this.log('All integration tests passed!', 'success');
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Test binary download and installation
|
|
117
|
+
*/
|
|
118
|
+
async function testBinaryDownloadAndInstall(runner, options = {}) {
|
|
119
|
+
await runner.test('Binary download and installation', async () => {
|
|
120
|
+
if (INTEGRATION_CONFIG.skipDownload) {
|
|
121
|
+
throw new Error('Download test skipped (--skip-download flag)');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Create clean test environment
|
|
125
|
+
fs.mkdirSync(INTEGRATION_CONFIG.testDir, { recursive: true });
|
|
126
|
+
|
|
127
|
+
// Copy package to test directory
|
|
128
|
+
const packageDir = path.join(__dirname, '..');
|
|
129
|
+
const testPackageDir = path.join(INTEGRATION_CONFIG.testDir, 'package');
|
|
130
|
+
|
|
131
|
+
await runner.runCommand('cp', ['-r', packageDir, testPackageDir]);
|
|
132
|
+
|
|
133
|
+
// Remove any existing binaries
|
|
134
|
+
const binDir = path.join(testPackageDir, 'bin');
|
|
135
|
+
if (fs.existsSync(binDir)) {
|
|
136
|
+
fs.rmSync(binDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Run installation
|
|
140
|
+
runner.log('Starting binary download...', 'download');
|
|
141
|
+
|
|
142
|
+
const installResult = await runner.runCommand('node', ['scripts/install.js'], {
|
|
143
|
+
cwd: testPackageDir,
|
|
144
|
+
timeout: 120000 // 2 minutes for download
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (installResult.code !== 0) {
|
|
148
|
+
throw new Error(`Installation failed with code ${installResult.code}: ${installResult.stderr}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Verify binary was downloaded
|
|
152
|
+
const { getPlatformInfo } = require('../scripts/install.js');
|
|
153
|
+
const platformInfo = getPlatformInfo();
|
|
154
|
+
const binaryPath = path.join(testPackageDir, 'bin', platformInfo.binaryName);
|
|
155
|
+
|
|
156
|
+
if (!fs.existsSync(binaryPath)) {
|
|
157
|
+
throw new Error(`Binary not downloaded: ${binaryPath}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Check binary size
|
|
161
|
+
const stats = fs.statSync(binaryPath);
|
|
162
|
+
if (stats.size < 1024 * 1024) { // Less than 1MB
|
|
163
|
+
throw new Error(`Binary too small: ${stats.size} bytes`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Store binary path for other tests
|
|
167
|
+
runner.binaryPath = binaryPath;
|
|
168
|
+
|
|
169
|
+
runner.log(`Binary downloaded successfully: ${(stats.size / 1024 / 1024).toFixed(1)}MB`, 'debug');
|
|
170
|
+
}, { critical: !options.skip, skip: options.skip });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Test binary execution
|
|
175
|
+
*/
|
|
176
|
+
async function testBinaryExecution(runner) {
|
|
177
|
+
await runner.test('Binary execution', async () => {
|
|
178
|
+
if (INTEGRATION_CONFIG.skipDownload || !runner.binaryPath || !fs.existsSync(runner.binaryPath)) {
|
|
179
|
+
throw new Error('Binary not available - download was skipped or failed');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Test version command
|
|
183
|
+
const versionResult = await runner.runCommand(runner.binaryPath, ['--version'], {
|
|
184
|
+
timeout: 10000
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (versionResult.code !== 0) {
|
|
188
|
+
throw new Error(`Version command failed: ${versionResult.stderr}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!versionResult.stdout.toLowerCase().includes('local memory')) {
|
|
192
|
+
throw new Error(`Unexpected version output: ${versionResult.stdout}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Test help command
|
|
196
|
+
const helpResult = await runner.runCommand(runner.binaryPath, ['--help'], {
|
|
197
|
+
timeout: 10000
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (helpResult.code !== 0) {
|
|
201
|
+
throw new Error(`Help command failed: ${helpResult.stderr}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const helpOutput = helpResult.stdout.toLowerCase();
|
|
205
|
+
const expectedCommands = ['remember', 'search', 'license', 'start', 'setup'];
|
|
206
|
+
|
|
207
|
+
for (const cmd of expectedCommands) {
|
|
208
|
+
if (!helpOutput.includes(cmd)) {
|
|
209
|
+
throw new Error(`Help output missing command: ${cmd}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
runner.log('Binary executes correctly and shows expected commands', 'debug');
|
|
214
|
+
}, { skip: INTEGRATION_CONFIG.skipDownload });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Test license system integration
|
|
219
|
+
*/
|
|
220
|
+
async function testLicenseIntegration(runner) {
|
|
221
|
+
await runner.test('License system integration', async () => {
|
|
222
|
+
if (INTEGRATION_CONFIG.skipDownload || !runner.binaryPath) {
|
|
223
|
+
throw new Error('Binary not available - download was skipped or failed');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Test license help
|
|
227
|
+
const licenseHelpResult = await runner.runCommand(runner.binaryPath, ['license', '--help'], {
|
|
228
|
+
timeout: 10000
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (licenseHelpResult.code !== 0) {
|
|
232
|
+
throw new Error(`License help failed: ${licenseHelpResult.stderr}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const helpOutput = licenseHelpResult.stdout.toLowerCase();
|
|
236
|
+
const expectedSubcommands = ['activate', 'status', 'validate'];
|
|
237
|
+
|
|
238
|
+
for (const subcmd of expectedSubcommands) {
|
|
239
|
+
if (!helpOutput.includes(subcmd)) {
|
|
240
|
+
throw new Error(`License help missing subcommand: ${subcmd}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Test license validation with test key
|
|
245
|
+
const validateResult = await runner.runCommand(runner.binaryPath, ['license', 'validate', INTEGRATION_CONFIG.testLicense], {
|
|
246
|
+
timeout: 10000
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// The test license should pass format validation
|
|
250
|
+
if (validateResult.code !== 0) {
|
|
251
|
+
runner.log('License validation expected to fail for test key (this is normal)', 'debug');
|
|
252
|
+
} else {
|
|
253
|
+
runner.log('License validation completed', 'debug');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Test license status (should show no license)
|
|
257
|
+
const statusResult = await runner.runCommand(runner.binaryPath, ['license', 'status'], {
|
|
258
|
+
timeout: 10000
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Status command should work regardless of license state
|
|
262
|
+
runner.log('License commands are properly integrated', 'debug');
|
|
263
|
+
}, { skip: INTEGRATION_CONFIG.skipDownload });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Test MCP server functionality
|
|
268
|
+
*/
|
|
269
|
+
async function testMCPServer(runner) {
|
|
270
|
+
await runner.test('MCP server functionality', async () => {
|
|
271
|
+
if (INTEGRATION_CONFIG.skipDownload || !runner.binaryPath) {
|
|
272
|
+
throw new Error('Binary not available - download was skipped or failed');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Test MCP server help
|
|
276
|
+
const mcpResult = await runner.runCommand(runner.binaryPath, ['--help'], {
|
|
277
|
+
timeout: 10000
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (mcpResult.code !== 0) {
|
|
281
|
+
throw new Error(`MCP help failed: ${mcpResult.stderr}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Look for MCP-related content in help
|
|
285
|
+
const output = mcpResult.stdout.toLowerCase();
|
|
286
|
+
|
|
287
|
+
// The binary should be able to run in MCP mode
|
|
288
|
+
// We don't actually start the MCP server here to avoid complexity
|
|
289
|
+
// But we verify the binary has the capability
|
|
290
|
+
|
|
291
|
+
runner.log('Binary supports MCP server functionality', 'debug');
|
|
292
|
+
}, { skip: INTEGRATION_CONFIG.skipDownload });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Test NPM package API compatibility
|
|
297
|
+
*/
|
|
298
|
+
async function testPackageAPI(runner) {
|
|
299
|
+
await runner.test('NPM package API compatibility', async () => {
|
|
300
|
+
const testPackageDir = path.join(INTEGRATION_CONFIG.testDir, 'package');
|
|
301
|
+
|
|
302
|
+
if (INTEGRATION_CONFIG.skipDownload || !fs.existsSync(testPackageDir)) {
|
|
303
|
+
throw new Error('Test package directory not available - download was skipped or failed');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Test main entry point with binary present
|
|
307
|
+
process.chdir(testPackageDir);
|
|
308
|
+
|
|
309
|
+
const indexPath = path.resolve('./index.js');
|
|
310
|
+
delete require.cache[indexPath];
|
|
311
|
+
const indexModule = require(indexPath);
|
|
312
|
+
|
|
313
|
+
// Test getBinaryPath with actual binary
|
|
314
|
+
const binaryPath = indexModule.getBinaryPath();
|
|
315
|
+
|
|
316
|
+
if (!fs.existsSync(binaryPath)) {
|
|
317
|
+
throw new Error(`getBinaryPath returned non-existent path: ${binaryPath}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Test that binary path matches expected location
|
|
321
|
+
const expectedPath = runner.binaryPath;
|
|
322
|
+
if (path.resolve(binaryPath) !== path.resolve(expectedPath)) {
|
|
323
|
+
throw new Error(`Binary path mismatch: ${binaryPath} vs ${expectedPath}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
runner.log('NPM package API works correctly with downloaded binary', 'debug');
|
|
327
|
+
}, { skip: INTEGRATION_CONFIG.skipDownload });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Test cross-platform compatibility
|
|
332
|
+
*/
|
|
333
|
+
async function testCrossPlatformCompat(runner) {
|
|
334
|
+
await runner.test('Cross-platform compatibility', async () => {
|
|
335
|
+
const { getPlatformInfo } = require('../scripts/install.js');
|
|
336
|
+
const platformInfo = getPlatformInfo();
|
|
337
|
+
|
|
338
|
+
// Verify current platform is supported
|
|
339
|
+
const supportedPlatforms = {
|
|
340
|
+
'darwin-x64': 'local-memory-macos-intel',
|
|
341
|
+
'darwin-arm64': 'local-memory-macos-arm',
|
|
342
|
+
'linux-x64': 'local-memory-linux',
|
|
343
|
+
'win32-x64': 'local-memory-windows.exe'
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const platformKey = `${platformInfo.platform}-${platformInfo.arch}`;
|
|
347
|
+
const expectedBinary = supportedPlatforms[platformKey];
|
|
348
|
+
|
|
349
|
+
if (!expectedBinary) {
|
|
350
|
+
throw new Error(`Unsupported platform: ${platformKey}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (platformInfo.binaryName !== expectedBinary) {
|
|
354
|
+
throw new Error(`Binary name mismatch for ${platformKey}: expected ${expectedBinary}, got ${platformInfo.binaryName}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
runner.log(`Platform ${platformKey} correctly mapped to ${platformInfo.binaryName}`, 'debug');
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Main integration test runner
|
|
363
|
+
*/
|
|
364
|
+
async function runIntegrationTests() {
|
|
365
|
+
const runner = new IntegrationTestRunner();
|
|
366
|
+
|
|
367
|
+
console.log('๐งช Local Memory NPM Package - Integration Test Suite');
|
|
368
|
+
console.log('=====================================================');
|
|
369
|
+
console.log(`Test directory: ${INTEGRATION_CONFIG.testDir}`);
|
|
370
|
+
console.log(`Verbose: ${INTEGRATION_CONFIG.verbose}`);
|
|
371
|
+
console.log(`Skip download: ${INTEGRATION_CONFIG.skipDownload}`);
|
|
372
|
+
console.log(`Cleanup: ${INTEGRATION_CONFIG.cleanup}`);
|
|
373
|
+
console.log('');
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
// Cross-platform compatibility (no dependencies)
|
|
377
|
+
await testCrossPlatformCompat(runner);
|
|
378
|
+
|
|
379
|
+
// Binary download and installation (network required)
|
|
380
|
+
await testBinaryDownloadAndInstall(runner, {
|
|
381
|
+
skip: INTEGRATION_CONFIG.skipDownload
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Binary functionality tests
|
|
385
|
+
await testBinaryExecution(runner);
|
|
386
|
+
await testLicenseIntegration(runner);
|
|
387
|
+
await testMCPServer(runner);
|
|
388
|
+
|
|
389
|
+
// Package API tests
|
|
390
|
+
await testPackageAPI(runner);
|
|
391
|
+
|
|
392
|
+
} catch (error) {
|
|
393
|
+
runner.log(`Critical error: ${error.message}`, 'error');
|
|
394
|
+
if (INTEGRATION_CONFIG.verbose) {
|
|
395
|
+
console.error(error.stack);
|
|
396
|
+
}
|
|
397
|
+
return false;
|
|
398
|
+
} finally {
|
|
399
|
+
// Cleanup
|
|
400
|
+
if (INTEGRATION_CONFIG.cleanup && fs.existsSync(INTEGRATION_CONFIG.testDir)) {
|
|
401
|
+
try {
|
|
402
|
+
fs.rmSync(INTEGRATION_CONFIG.testDir, { recursive: true, force: true });
|
|
403
|
+
runner.log('Cleaned up test directory', 'debug');
|
|
404
|
+
} catch (error) {
|
|
405
|
+
runner.log(`Cleanup warning: ${error.message}`, 'warning');
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return runner.summary();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Run tests if executed directly
|
|
414
|
+
if (require.main === module) {
|
|
415
|
+
runIntegrationTests().then(success => {
|
|
416
|
+
process.exit(success ? 0 : 1);
|
|
417
|
+
}).catch(error => {
|
|
418
|
+
console.error('Integration test runner crashed:', error);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
module.exports = { runIntegrationTests, IntegrationTestRunner };
|