From 96cb9eca0f87cc37456a4f887aaf1614d492164e Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Tue, 7 Oct 2025 10:53:28 +0200 Subject: [PATCH] test: update unit test for nodeName field in validation response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update expected validation response to include nodeName field in warnings. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/test-docker-fingerprint.ts | 163 ++++++++++++++++++++ tests/unit/mcp/handlers-n8n-manager.test.ts | 1 + 2 files changed, 164 insertions(+) create mode 100644 scripts/test-docker-fingerprint.ts diff --git a/scripts/test-docker-fingerprint.ts b/scripts/test-docker-fingerprint.ts new file mode 100644 index 0000000..30b9e14 --- /dev/null +++ b/scripts/test-docker-fingerprint.ts @@ -0,0 +1,163 @@ +/** + * Test Docker Host Fingerprinting + * Verifies that host machine characteristics are stable across container recreations + */ + +import { existsSync, readFileSync } from 'fs'; +import { platform, arch } from 'os'; +import { createHash } from 'crypto'; + +console.log('=== Docker Host Fingerprinting Test ===\n'); + +function generateHostFingerprint(): string { + try { + const signals: string[] = []; + + console.log('Collecting host signals...\n'); + + // CPU info (stable across container recreations) + if (existsSync('/proc/cpuinfo')) { + const cpuinfo = readFileSync('/proc/cpuinfo', 'utf-8'); + const modelMatch = cpuinfo.match(/model name\s*:\s*(.+)/); + const coresMatch = cpuinfo.match(/processor\s*:/g); + + if (modelMatch) { + const cpuModel = modelMatch[1].trim(); + signals.push(cpuModel); + console.log('✓ CPU Model:', cpuModel); + } + + if (coresMatch) { + const cores = `cores:${coresMatch.length}`; + signals.push(cores); + console.log('✓ CPU Cores:', coresMatch.length); + } + } else { + console.log('✗ /proc/cpuinfo not available (Windows/Mac Docker)'); + } + + // Memory (stable) + if (existsSync('/proc/meminfo')) { + const meminfo = readFileSync('/proc/meminfo', 'utf-8'); + const totalMatch = meminfo.match(/MemTotal:\s+(\d+)/); + + if (totalMatch) { + const memory = `mem:${totalMatch[1]}`; + signals.push(memory); + console.log('✓ Total Memory:', totalMatch[1], 'kB'); + } + } else { + console.log('✗ /proc/meminfo not available (Windows/Mac Docker)'); + } + + // Docker network subnet + const networkInfo = getDockerNetworkInfo(); + if (networkInfo) { + signals.push(networkInfo); + console.log('✓ Network Info:', networkInfo); + } else { + console.log('✗ Network info not available'); + } + + // Platform basics (stable) + signals.push(platform(), arch()); + console.log('✓ Platform:', platform()); + console.log('✓ Architecture:', arch()); + + // Generate stable ID from all signals + console.log('\nCombined signals:', signals.join(' | ')); + const fingerprint = signals.join('-'); + const userId = createHash('sha256').update(fingerprint).digest('hex').substring(0, 16); + + return userId; + + } catch (error) { + console.error('Error generating fingerprint:', error); + // Fallback + return createHash('sha256') + .update(`${platform()}-${arch()}-docker`) + .digest('hex') + .substring(0, 16); + } +} + +function getDockerNetworkInfo(): string | null { + try { + // Read routing table to get bridge network + if (existsSync('/proc/net/route')) { + const routes = readFileSync('/proc/net/route', 'utf-8'); + const lines = routes.split('\n'); + + for (const line of lines) { + if (line.includes('eth0')) { + const parts = line.split(/\s+/); + if (parts[2]) { + const gateway = parseInt(parts[2], 16).toString(16); + return `net:${gateway}`; + } + } + } + } + } catch { + // Ignore errors + } + return null; +} + +// Test environment detection +console.log('\n=== Environment Detection ===\n'); + +const isDocker = process.env.IS_DOCKER === 'true'; +const isCloudEnvironment = !!( + process.env.RAILWAY_ENVIRONMENT || + process.env.RENDER || + process.env.FLY_APP_NAME || + process.env.HEROKU_APP_NAME || + process.env.AWS_EXECUTION_ENV || + process.env.KUBERNETES_SERVICE_HOST +); + +console.log('IS_DOCKER env:', process.env.IS_DOCKER); +console.log('Docker detected:', isDocker); +console.log('Cloud environment:', isCloudEnvironment); + +// Generate fingerprints +console.log('\n=== Fingerprint Generation ===\n'); + +const fingerprint1 = generateHostFingerprint(); +const fingerprint2 = generateHostFingerprint(); +const fingerprint3 = generateHostFingerprint(); + +console.log('\nFingerprint 1:', fingerprint1); +console.log('Fingerprint 2:', fingerprint2); +console.log('Fingerprint 3:', fingerprint3); + +const consistent = fingerprint1 === fingerprint2 && fingerprint2 === fingerprint3; +console.log('\nConsistent:', consistent ? '✓ YES' : '✗ NO'); + +// Test explicit ID override +console.log('\n=== Environment Variable Override Test ===\n'); + +if (process.env.N8N_MCP_USER_ID) { + console.log('Explicit user ID:', process.env.N8N_MCP_USER_ID); + console.log('This would override the fingerprint'); +} else { + console.log('No explicit user ID set'); + console.log('To test: N8N_MCP_USER_ID=my-custom-id npx tsx ' + process.argv[1]); +} + +// Stability estimate +console.log('\n=== Stability Analysis ===\n'); + +const hasStableSignals = existsSync('/proc/cpuinfo') || existsSync('/proc/meminfo'); +if (hasStableSignals) { + console.log('✓ Host-based signals available'); + console.log('✓ Fingerprint should be stable across container recreations'); + console.log('✓ Different fingerprints on different physical hosts'); +} else { + console.log('⚠️ Limited host signals (Windows/Mac Docker Desktop)'); + console.log('⚠️ Fingerprint may not be fully stable'); + console.log('💡 Recommendation: Use N8N_MCP_USER_ID env var for stability'); +} + +console.log('\n'); diff --git a/tests/unit/mcp/handlers-n8n-manager.test.ts b/tests/unit/mcp/handlers-n8n-manager.test.ts index 0201bc1..20287d5 100644 --- a/tests/unit/mcp/handlers-n8n-manager.test.ts +++ b/tests/unit/mcp/handlers-n8n-manager.test.ts @@ -980,6 +980,7 @@ describe('handlers-n8n-manager', () => { warnings: [ { node: 'node1', + nodeName: 'node1', message: 'Consider using newer version', details: { currentVersion: 1, latestVersion: 2 }, },