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.
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Local Memory Server NPM Package - Comprehensive Test Suite
5
+ *
6
+ * This test suite validates the npm package installation, binary downloads,
7
+ * platform detection, and basic functionality across different scenarios.
8
+ */
9
+
10
+ const { spawn, exec } = require('child_process');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+ const https = require('https');
15
+
16
+ // Test configuration
17
+ const TEST_CONFIG = {
18
+ timeout: 30000,
19
+ verbose: process.argv.includes('--verbose') || process.env.VERBOSE === 'true',
20
+ cleanup: !process.argv.includes('--no-cleanup'),
21
+ testDir: path.join(os.tmpdir(), `local-memory-npm-test-${Date.now()}`)
22
+ };
23
+
24
+ /**
25
+ * Test utilities
26
+ */
27
+ class TestRunner {
28
+ constructor() {
29
+ this.tests = [];
30
+ this.passed = 0;
31
+ this.failed = 0;
32
+ this.skipped = 0;
33
+ }
34
+
35
+ log(message, level = 'info') {
36
+ const timestamp = new Date().toISOString();
37
+ const prefix = {
38
+ info: '๐Ÿ“‹',
39
+ success: 'โœ…',
40
+ error: 'โŒ',
41
+ warning: 'โš ๏ธ',
42
+ debug: '๐Ÿ”'
43
+ }[level] || '๐Ÿ“‹';
44
+
45
+ console.log(`${prefix} ${message}`);
46
+
47
+ if (level === 'debug' && !TEST_CONFIG.verbose) {
48
+ return; // Skip debug messages unless verbose
49
+ }
50
+ }
51
+
52
+ async runCommand(command, args = [], options = {}) {
53
+ return new Promise((resolve, reject) => {
54
+ const proc = spawn(command, args, {
55
+ stdio: TEST_CONFIG.verbose ? 'inherit' : 'pipe',
56
+ timeout: TEST_CONFIG.timeout,
57
+ ...options
58
+ });
59
+
60
+ let stdout = '';
61
+ let stderr = '';
62
+
63
+ if (!TEST_CONFIG.verbose) {
64
+ proc.stdout?.on('data', data => stdout += data.toString());
65
+ proc.stderr?.on('data', data => stderr += data.toString());
66
+ }
67
+
68
+ proc.on('close', code => resolve({ code, stdout, stderr }));
69
+ proc.on('error', reject);
70
+ });
71
+ }
72
+
73
+ async test(name, testFn, options = {}) {
74
+ this.log(`Running: ${name}`);
75
+
76
+ try {
77
+ await testFn();
78
+ this.passed++;
79
+ this.log(`PASS: ${name}`, 'success');
80
+ } catch (error) {
81
+ this.failed++;
82
+ this.log(`FAIL: ${name} - ${error.message}`, 'error');
83
+ if (TEST_CONFIG.verbose) {
84
+ console.error(error.stack);
85
+ }
86
+
87
+ if (options.critical) {
88
+ throw new Error(`Critical test failed: ${name}`);
89
+ }
90
+ }
91
+ }
92
+
93
+ summary() {
94
+ const total = this.passed + this.failed + this.skipped;
95
+ this.log(`\nTest Summary: ${this.passed}/${total} passed, ${this.failed} failed, ${this.skipped} skipped`);
96
+
97
+ if (this.failed > 0) {
98
+ this.log('Some tests failed!', 'error');
99
+ process.exit(1);
100
+ } else {
101
+ this.log('All tests passed!', 'success');
102
+ process.exit(0);
103
+ }
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Test functions
109
+ */
110
+ async function testPackageStructure(runner) {
111
+ await runner.test('Package structure validation', async () => {
112
+ const packageDir = path.join(__dirname, '..');
113
+
114
+ // Check required files exist
115
+ const requiredFiles = [
116
+ 'package.json',
117
+ 'README.md',
118
+ 'CHANGELOG.md',
119
+ 'index.js',
120
+ 'bin/local-memory',
121
+ 'scripts/install.js',
122
+ 'scripts/test.js',
123
+ 'scripts/uninstall.js'
124
+ ];
125
+
126
+ for (const file of requiredFiles) {
127
+ const filePath = path.join(packageDir, file);
128
+ if (!fs.existsSync(filePath)) {
129
+ throw new Error(`Missing required file: ${file}`);
130
+ }
131
+ }
132
+
133
+ // Validate package.json
134
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'));
135
+
136
+ if (packageJson.name !== '@local-memory/server') {
137
+ throw new Error('Invalid package name');
138
+ }
139
+
140
+ if (!packageJson.version || !packageJson.version.match(/^\d+\.\d+\.\d+/)) {
141
+ throw new Error('Invalid package version');
142
+ }
143
+
144
+ if (!packageJson.bin || !packageJson.bin['local-memory']) {
145
+ throw new Error('Missing binary configuration');
146
+ }
147
+
148
+ runner.log('Package structure is valid', 'debug');
149
+ });
150
+ }
151
+
152
+ async function testPlatformDetection(runner) {
153
+ await runner.test('Platform detection', async () => {
154
+ const { getPlatformInfo } = require('../scripts/install.js');
155
+
156
+ const platformInfo = getPlatformInfo();
157
+
158
+ if (!platformInfo.binaryName) {
159
+ throw new Error('Platform detection failed - no binary name');
160
+ }
161
+
162
+ if (!platformInfo.platformPath) {
163
+ throw new Error('Platform detection failed - no platform path');
164
+ }
165
+
166
+ // Verify binary name follows expected pattern
167
+ const validPatterns = [
168
+ /^local-memory-linux$/,
169
+ /^local-memory-macos-(arm|intel)$/,
170
+ /^local-memory-windows\.exe$/
171
+ ];
172
+
173
+ const isValid = validPatterns.some(pattern => pattern.test(platformInfo.binaryName));
174
+ if (!isValid) {
175
+ throw new Error(`Invalid binary name for platform: ${platformInfo.binaryName}`);
176
+ }
177
+
178
+ runner.log(`Detected platform: ${platformInfo.platform}-${platformInfo.arch} -> ${platformInfo.binaryName}`, 'debug');
179
+ });
180
+ }
181
+
182
+ async function testInstallationSimulation(runner) {
183
+ await runner.test('Installation simulation', async () => {
184
+ // Create temporary test environment
185
+ fs.mkdirSync(TEST_CONFIG.testDir, { recursive: true });
186
+
187
+ // Copy package files to test directory
188
+ const packageDir = path.join(__dirname, '..');
189
+ const testPackageDir = path.join(TEST_CONFIG.testDir, 'package');
190
+
191
+ // Copy package structure (without binaries for simulation)
192
+ await runner.runCommand('cp', ['-r', packageDir, testPackageDir]);
193
+
194
+ // Remove any existing binaries to simulate fresh install
195
+ const binDir = path.join(testPackageDir, 'bin');
196
+ if (fs.existsSync(binDir)) {
197
+ fs.rmSync(binDir, { recursive: true, force: true });
198
+ }
199
+
200
+ // Test that installation script can detect platform
201
+ const originalCwd = process.cwd();
202
+ process.chdir(testPackageDir);
203
+
204
+ const installScriptPath = path.join(testPackageDir, 'scripts', 'install.js');
205
+ delete require.cache[installScriptPath];
206
+ const { getPlatformInfo } = require(installScriptPath);
207
+ const platformInfo = getPlatformInfo();
208
+
209
+ if (!platformInfo || !platformInfo.binaryName) {
210
+ throw new Error('Installation simulation failed - platform detection');
211
+ }
212
+
213
+ runner.log('Installation simulation passed', 'debug');
214
+
215
+ // Cleanup
216
+ process.chdir(originalCwd);
217
+ if (TEST_CONFIG.cleanup) {
218
+ fs.rmSync(TEST_CONFIG.testDir, { recursive: true, force: true });
219
+ }
220
+ });
221
+ }
222
+
223
+ async function testMainEntryPoint(runner) {
224
+ await runner.test('Main entry point functionality', async () => {
225
+ const indexPath = path.join(__dirname, '..', 'index.js');
226
+
227
+ // Test that index.js can be required without errors
228
+ delete require.cache[require.resolve(indexPath)];
229
+ const indexModule = require(indexPath);
230
+
231
+ if (typeof indexModule.getBinaryName !== 'function') {
232
+ throw new Error('Missing getBinaryName function');
233
+ }
234
+
235
+ if (typeof indexModule.getBinaryPath !== 'function') {
236
+ throw new Error('Missing getBinaryPath function');
237
+ }
238
+
239
+ if (typeof indexModule.main !== 'function') {
240
+ throw new Error('Missing main function');
241
+ }
242
+
243
+ // Test binary name detection
244
+ const binaryName = indexModule.getBinaryName();
245
+ if (!binaryName || typeof binaryName !== 'string') {
246
+ throw new Error('Invalid binary name detection');
247
+ }
248
+
249
+ runner.log(`Entry point functions work correctly, binary name: ${binaryName}`, 'debug');
250
+ });
251
+ }
252
+
253
+ async function testScriptFunctionality(runner) {
254
+ await runner.test('Script functionality', async () => {
255
+ const scriptsDir = path.join(__dirname, '..', 'scripts');
256
+
257
+ // Test that all scripts can be required
258
+ const scripts = ['install.js', 'test.js', 'uninstall.js'];
259
+
260
+ for (const script of scripts) {
261
+ const scriptPath = path.join(scriptsDir, script);
262
+
263
+ if (!fs.existsSync(scriptPath)) {
264
+ throw new Error(`Missing script: ${script}`);
265
+ }
266
+
267
+ // Check that script is executable
268
+ const stats = fs.statSync(scriptPath);
269
+ if (!(stats.mode & 0o111)) {
270
+ // Not executable - this is okay for scripts, they're run with node
271
+ runner.log(`Script ${script} is not executable (but will work with node)`, 'debug');
272
+ }
273
+
274
+ // Test that script can be required (basic syntax check)
275
+ try {
276
+ delete require.cache[require.resolve(scriptPath)];
277
+ require(scriptPath);
278
+ } catch (error) {
279
+ throw new Error(`Script ${script} has syntax errors: ${error.message}`);
280
+ }
281
+ }
282
+
283
+ runner.log('All scripts have valid syntax', 'debug');
284
+ });
285
+ }
286
+
287
+ async function testDownloadURLGeneration(runner) {
288
+ await runner.test('Download URL generation', async () => {
289
+ const { getPlatformInfo } = require('../scripts/install.js');
290
+
291
+ const platformInfo = getPlatformInfo();
292
+
293
+ // Test GitHub releases URL generation
294
+ const githubUrl = `https://github.com/danieleugenewilliams/local-memory-golang/releases/latest/download/${platformInfo.binaryName}`;
295
+
296
+ // Test that URL is properly formatted
297
+ if (!githubUrl.includes(platformInfo.binaryName)) {
298
+ throw new Error('GitHub URL does not include binary name');
299
+ }
300
+
301
+ if (!githubUrl.startsWith('https://')) {
302
+ throw new Error('GitHub URL is not HTTPS');
303
+ }
304
+
305
+ runner.log(`Generated GitHub fallback URL: ${githubUrl}`, 'debug');
306
+
307
+ // We don't test the actual download here to avoid network dependencies
308
+ // That will be tested in the integration test phase
309
+ });
310
+ }
311
+
312
+ async function testPackageJsonValidation(runner) {
313
+ await runner.test('package.json validation', async () => {
314
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
315
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
316
+
317
+ // Validate required fields
318
+ const requiredFields = [
319
+ 'name', 'version', 'description', 'keywords', 'author', 'license',
320
+ 'homepage', 'repository', 'bugs', 'engines', 'main', 'bin', 'scripts', 'files'
321
+ ];
322
+
323
+ for (const field of requiredFields) {
324
+ if (!packageJson[field]) {
325
+ throw new Error(`Missing required field: ${field}`);
326
+ }
327
+ }
328
+
329
+ // Validate specific values
330
+ if (!packageJson.keywords.includes('mcp')) {
331
+ throw new Error('Package should include "mcp" keyword');
332
+ }
333
+
334
+ if (!packageJson.engines.node) {
335
+ throw new Error('Missing Node.js engine requirement');
336
+ }
337
+
338
+ if (!packageJson.scripts.postinstall) {
339
+ throw new Error('Missing postinstall script');
340
+ }
341
+
342
+ if (!packageJson.mcp || !packageJson.mcp.server) {
343
+ throw new Error('Missing MCP server metadata');
344
+ }
345
+
346
+ runner.log('package.json validation passed', 'debug');
347
+ });
348
+ }
349
+
350
+ /**
351
+ * Main test runner
352
+ */
353
+ async function runTests() {
354
+ const runner = new TestRunner();
355
+
356
+ console.log('๐Ÿงช Local Memory NPM Package - Comprehensive Test Suite');
357
+ console.log('====================================================');
358
+ console.log(`Test directory: ${TEST_CONFIG.testDir}`);
359
+ console.log(`Verbose: ${TEST_CONFIG.verbose}`);
360
+ console.log(`Cleanup: ${TEST_CONFIG.cleanup}`);
361
+ console.log('');
362
+
363
+ try {
364
+ // Structure and validation tests
365
+ await testPackageStructure(runner);
366
+ await testPackageJsonValidation(runner);
367
+
368
+ // Platform and installation tests
369
+ await testPlatformDetection(runner);
370
+ await testMainEntryPoint(runner);
371
+ await testScriptFunctionality(runner);
372
+
373
+ // URL and download tests (without network)
374
+ await testDownloadURLGeneration(runner);
375
+
376
+ // Installation simulation
377
+ await testInstallationSimulation(runner);
378
+
379
+ } catch (error) {
380
+ runner.log(`Critical error: ${error.message}`, 'error');
381
+ if (TEST_CONFIG.verbose) {
382
+ console.error(error.stack);
383
+ }
384
+ process.exit(1);
385
+ } finally {
386
+ // Cleanup
387
+ if (TEST_CONFIG.cleanup && fs.existsSync(TEST_CONFIG.testDir)) {
388
+ try {
389
+ fs.rmSync(TEST_CONFIG.testDir, { recursive: true, force: true });
390
+ runner.log('Cleaned up test directory', 'debug');
391
+ } catch (error) {
392
+ runner.log(`Cleanup warning: ${error.message}`, 'warning');
393
+ }
394
+ }
395
+ }
396
+
397
+ runner.summary();
398
+ }
399
+
400
+ // Run tests if executed directly
401
+ if (require.main === module) {
402
+ runTests().catch(error => {
403
+ console.error('Test runner crashed:', error);
404
+ process.exit(1);
405
+ });
406
+ }
407
+
408
+ module.exports = { runTests, TestRunner };