631 lines
22 KiB
JavaScript
631 lines
22 KiB
JavaScript
/**
|
|
* Tests for npm/install.js and npm/utils.js
|
|
* Tests platform detection, download patterns, extraction, and CLI launcher flow
|
|
*/
|
|
|
|
const os = require('os')
|
|
const path = require('path')
|
|
const fs = require('fs')
|
|
const tar = require('tar')
|
|
const {
|
|
isWindows7OrEarlier,
|
|
isMacOS10,
|
|
isLinuxLegacy,
|
|
sanitizeVersion,
|
|
sanitizeFilename,
|
|
getElectermExePath,
|
|
isElectermExtracted,
|
|
_packageRoot,
|
|
_extractDir
|
|
} = require('../../npm/install')
|
|
|
|
const {
|
|
httpGet,
|
|
extractTarGz,
|
|
download,
|
|
phin,
|
|
applyProxy,
|
|
formatBytes
|
|
} = require('../../npm/utils')
|
|
|
|
const plat = os.platform()
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
let passed = 0
|
|
let failed = 0
|
|
const asyncQueue = []
|
|
|
|
function test (name, fn) {
|
|
try {
|
|
fn()
|
|
console.log(` ✓ ${name}`)
|
|
passed++
|
|
} catch (e) {
|
|
console.error(` ✗ ${name}`)
|
|
console.error(` Error: ${e.message}`)
|
|
failed++
|
|
}
|
|
}
|
|
|
|
function testAsync (name, fn) {
|
|
asyncQueue.push(async () => {
|
|
try {
|
|
await fn()
|
|
console.log(` ✓ ${name}`)
|
|
passed++
|
|
} catch (e) {
|
|
console.error(` ✗ ${name}`)
|
|
console.error(` Error: ${e.message}`)
|
|
failed++
|
|
}
|
|
})
|
|
}
|
|
|
|
function expect (actual) {
|
|
return {
|
|
toBe (expected) {
|
|
if (actual !== expected) {
|
|
throw new Error(`Expected ${expected} but got ${actual}`)
|
|
}
|
|
},
|
|
toEqual (expected) {
|
|
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
|
|
throw new Error(`Expected ${JSON.stringify(expected)} but got ${JSON.stringify(actual)}`)
|
|
}
|
|
},
|
|
toContain (expected) {
|
|
if (!actual.includes(expected)) {
|
|
throw new Error(`Expected ${actual} to contain ${expected}`)
|
|
}
|
|
},
|
|
toMatch (pattern) {
|
|
if (!pattern.test(actual)) {
|
|
throw new Error(`Expected ${actual} to match ${pattern}`)
|
|
}
|
|
},
|
|
toBeTruthy () {
|
|
if (!actual) {
|
|
throw new Error(`Expected ${actual} to be truthy`)
|
|
}
|
|
},
|
|
toBeFalsy () {
|
|
if (actual) {
|
|
throw new Error(`Expected ${actual} to be falsy`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function testThrows (fn, expectedMessage) {
|
|
try {
|
|
fn()
|
|
throw new Error('Expected function to throw')
|
|
} catch (e) {
|
|
if (e.message === 'Expected function to throw') throw e
|
|
if (expectedMessage && !e.message.includes(expectedMessage)) {
|
|
throw new Error(`Expected error to include "${expectedMessage}" but got "${e.message}"`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Download pattern helper (for testing)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function getDownloadPattern (platform, architecture, options = {}) {
|
|
const { win7, mac10, linuxLegacy } = options
|
|
|
|
if (platform === 'win32') {
|
|
if (win7) {
|
|
return { pattern: /electerm-\d+\.\d+\.\d+-win7\.tar\.gz$/, type: 'win7' }
|
|
} else if (architecture === 'arm64') {
|
|
return { pattern: /electerm-\d+\.\d+\.\d+-win-arm64\.tar\.gz$/, type: 'win-arm64' }
|
|
} else {
|
|
return { pattern: /electerm-\d+\.\d+\.\d+-win-x64\.tar\.gz$/, type: 'win-x64' }
|
|
}
|
|
} else if (platform === 'darwin') {
|
|
if (mac10) {
|
|
return { pattern: /mac10-x64\.dmg$/, type: 'mac10-x64' }
|
|
} else if (architecture === 'arm64') {
|
|
return { pattern: /mac-arm64\.dmg$/, type: 'mac-arm64' }
|
|
} else {
|
|
return { pattern: /mac-x64\.dmg$/, type: 'mac-x64' }
|
|
}
|
|
} else if (platform === 'linux') {
|
|
const suffix = linuxLegacy ? '-legacy' : ''
|
|
if (architecture === 'arm64' || architecture === 'aarch64') {
|
|
return { pattern: new RegExp(`linux-arm64${suffix}\\.tar\\.gz$`), type: `linux-arm64${suffix}` }
|
|
} else if (architecture === 'arm') {
|
|
return { pattern: new RegExp(`linux-armv7l${suffix}\\.tar\\.gz$`), type: `linux-armv7l${suffix}` }
|
|
} else if (architecture === 'loong64') {
|
|
return { pattern: new RegExp(`linux-loong64${suffix}\\.tar\\.gz$`), type: `linux-loong64${suffix}` }
|
|
} else {
|
|
return { pattern: new RegExp(`linux-x64${suffix}\\.tar\\.gz$`), type: `linux-x64${suffix}` }
|
|
}
|
|
}
|
|
|
|
return { pattern: null, type: 'unsupported' }
|
|
}
|
|
|
|
// =============================================================================
|
|
// Tests: Platform Detection
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Platform Detection Tests ===\n')
|
|
|
|
test('isWindows7OrEarlier: returns false for non-Windows platforms', () => {
|
|
expect(isWindows7OrEarlier('darwin', '21.0.0')).toBe(false)
|
|
expect(isWindows7OrEarlier('linux', '5.4.0')).toBe(false)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns true for Windows 7 (NT 6.1)', () => {
|
|
expect(isWindows7OrEarlier('win32', '6.1.7601')).toBe(true)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns true for Windows Vista (NT 6.0)', () => {
|
|
expect(isWindows7OrEarlier('win32', '6.0.6000')).toBe(true)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns true for Windows XP (NT 5.1)', () => {
|
|
expect(isWindows7OrEarlier('win32', '5.1.2600')).toBe(true)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns false for Windows 8 (NT 6.2)', () => {
|
|
expect(isWindows7OrEarlier('win32', '6.2.9200')).toBe(false)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns false for Windows 10 (NT 10.0)', () => {
|
|
expect(isWindows7OrEarlier('win32', '10.0.19041')).toBe(false)
|
|
})
|
|
|
|
test('isWindows7OrEarlier: returns false for Windows 11 (NT 10.0)', () => {
|
|
expect(isWindows7OrEarlier('win32', '10.0.22000')).toBe(false)
|
|
})
|
|
|
|
test('isMacOS10: returns false for non-macOS platforms', () => {
|
|
expect(isMacOS10('win32', '10.0.19041')).toBe(false)
|
|
expect(isMacOS10('linux', '5.4.0')).toBe(false)
|
|
})
|
|
|
|
test('isMacOS10: returns true for macOS 10.15 Catalina (Darwin 19.x)', () => {
|
|
expect(isMacOS10('darwin', '19.6.0')).toBe(true)
|
|
})
|
|
|
|
test('isMacOS10: returns true for macOS 10.14 Mojave (Darwin 18.x)', () => {
|
|
expect(isMacOS10('darwin', '18.7.0')).toBe(true)
|
|
})
|
|
|
|
test('isMacOS10: returns false for macOS 11 Big Sur (Darwin 20.x)', () => {
|
|
expect(isMacOS10('darwin', '20.6.0')).toBe(false)
|
|
})
|
|
|
|
test('isMacOS10: returns false for macOS 14 Sonoma (Darwin 23.x)', () => {
|
|
expect(isMacOS10('darwin', '23.0.0')).toBe(false)
|
|
})
|
|
|
|
test('isLinuxLegacy: returns false for non-Linux platforms', () => {
|
|
expect(isLinuxLegacy('win32')).toBe(false)
|
|
expect(isLinuxLegacy('darwin')).toBe(false)
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Security Sanitization
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Security Sanitization Tests ===\n')
|
|
|
|
test('sanitizeVersion: removes v prefix', () => {
|
|
expect(sanitizeVersion('v1.2.3')).toBe('1.2.3')
|
|
})
|
|
|
|
test('sanitizeVersion: trims whitespace', () => {
|
|
expect(sanitizeVersion(' 1.2.3 ')).toBe('1.2.3')
|
|
})
|
|
|
|
test('sanitizeVersion: passes valid semver', () => {
|
|
expect(sanitizeVersion('1.2.3')).toBe('1.2.3')
|
|
expect(sanitizeVersion('3.2.0')).toBe('3.2.0')
|
|
})
|
|
|
|
test('sanitizeVersion: throws on invalid version', () => {
|
|
testThrows(() => sanitizeVersion('1.2'), 'validation')
|
|
testThrows(() => sanitizeVersion('abc'), 'validation')
|
|
testThrows(() => sanitizeVersion('1.2.3.4'), 'validation')
|
|
testThrows(() => sanitizeVersion('1.2.3-beta'), 'validation')
|
|
})
|
|
|
|
test('sanitizeFilename: passes valid filenames', () => {
|
|
expect(sanitizeFilename('electerm-3.2.0-linux-x64.tar.gz')).toBe('electerm-3.2.0-linux-x64.tar.gz')
|
|
expect(sanitizeFilename('electerm-3.2.0-mac-x64.dmg')).toBe('electerm-3.2.0-mac-x64.dmg')
|
|
})
|
|
|
|
test('sanitizeFilename: trims whitespace', () => {
|
|
expect(sanitizeFilename(' test.tar.gz ')).toBe('test.tar.gz')
|
|
})
|
|
|
|
test('sanitizeFilename: throws on invalid filenames', () => {
|
|
testThrows(() => sanitizeFilename('../evil.sh'), 'validation')
|
|
testThrows(() => sanitizeFilename('test; rm -rf /'), 'validation')
|
|
testThrows(() => sanitizeFilename('test$(whoami).tar.gz'), 'validation')
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Download Patterns
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Download Pattern Tests ===\n')
|
|
|
|
const v = '3.2.0'
|
|
const releaseFiles = [
|
|
`electerm-${v}-linux-arm64.tar.gz`,
|
|
`electerm-${v}-linux-arm64-legacy.tar.gz`,
|
|
`electerm-${v}-linux-armv7l.tar.gz`,
|
|
`electerm-${v}-linux-armv7l-legacy.tar.gz`,
|
|
`electerm-${v}-linux-loong64.tar.gz`,
|
|
`electerm-${v}-linux-loong64-legacy.tar.gz`,
|
|
`electerm-${v}-linux-x64.tar.gz`,
|
|
`electerm-${v}-linux-x64-legacy.tar.gz`,
|
|
`electerm-${v}-mac-arm64.dmg`,
|
|
`electerm-${v}-mac-x64.dmg`,
|
|
`electerm-${v}-mac10-x64.dmg`,
|
|
`electerm-${v}-win-arm64.tar.gz`,
|
|
`electerm-${v}-win-x64.tar.gz`,
|
|
`electerm-${v}-win7.tar.gz`
|
|
]
|
|
|
|
test('pattern: win-x64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('win32', 'x64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-win-x64.tar.gz`])
|
|
})
|
|
|
|
test('pattern: win-arm64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('win32', 'arm64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-win-arm64.tar.gz`])
|
|
})
|
|
|
|
test('pattern: win7 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('win32', 'x64', { win7: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-win7.tar.gz`])
|
|
})
|
|
|
|
test('pattern: mac-x64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('darwin', 'x64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-mac-x64.dmg`])
|
|
})
|
|
|
|
test('pattern: mac-arm64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('darwin', 'arm64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-mac-arm64.dmg`])
|
|
})
|
|
|
|
test('pattern: mac10-x64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('darwin', 'x64', { mac10: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-mac10-x64.dmg`])
|
|
})
|
|
|
|
test('pattern: linux-x64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'x64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-x64.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-x64-legacy matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'x64', { linuxLegacy: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-x64-legacy.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-arm64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'arm64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-arm64.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-arm64-legacy matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'arm64', { linuxLegacy: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-arm64-legacy.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-armv7l matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'arm', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-armv7l.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-armv7l-legacy matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'arm', { linuxLegacy: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-armv7l-legacy.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-loong64 matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'loong64', {})
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-loong64.tar.gz`])
|
|
})
|
|
|
|
test('pattern: linux-loong64-legacy matches exactly one file', () => {
|
|
const { pattern } = getDownloadPattern('linux', 'loong64', { linuxLegacy: true })
|
|
const matches = releaseFiles.filter(f => pattern.test(f))
|
|
expect(matches).toEqual([`electerm-${v}-linux-loong64-legacy.tar.gz`])
|
|
})
|
|
|
|
test('pattern: unsupported platform returns null pattern', () => {
|
|
const { pattern, type } = getDownloadPattern('freebsd', 'x64', {})
|
|
expect(pattern).toBe(null)
|
|
expect(type).toBe('unsupported')
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Extracted Binary Path
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Extracted Binary Path Tests ===\n')
|
|
|
|
test('getElectermExePath: returns correct path for current platform', () => {
|
|
const exePath = getElectermExePath()
|
|
if (plat === 'win32') {
|
|
expect(exePath).toBe(path.join(_extractDir, 'electerm.exe'))
|
|
} else {
|
|
expect(exePath).toBe(path.join(_extractDir, 'electerm'))
|
|
}
|
|
})
|
|
|
|
test('getElectermExePath: extractDir is inside packageRoot', () => {
|
|
expect(_extractDir).toContain(_packageRoot)
|
|
expect(_extractDir).toBe(path.join(_packageRoot, 'electerm'))
|
|
})
|
|
|
|
test('isElectermExtracted: returns boolean', () => {
|
|
const result = isElectermExtracted()
|
|
expect(typeof result).toBe('boolean')
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Utils - Proxy Support
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Utils Proxy Tests ===\n')
|
|
|
|
test('applyProxy: returns original URL when no proxy configured', () => {
|
|
// GITHUB_PROXY is empty by default in tests
|
|
const result = applyProxy('https://github.com/test/file.tar.gz')
|
|
expect(result).toBe('https://github.com/test/file.tar.gz')
|
|
})
|
|
|
|
test('applyProxy: does not proxy non-GitHub URLs', () => {
|
|
const result = applyProxy('https://example.com/file.tar.gz')
|
|
expect(result).toBe('https://example.com/file.tar.gz')
|
|
})
|
|
|
|
test('applyProxy: proxies GitHub URLs when GITHUB_PROXY is set', () => {
|
|
// Test the logic directly since module caches GITHUB_PROXY at load time
|
|
const proxy = 'https://electerm-mirror.html5beta.com'
|
|
const url = 'https://github.com/electerm/electerm/releases/download/v1.0.0/test.tar.gz'
|
|
|
|
// Simulate the applyProxy logic
|
|
const cleanProxy = proxy.replace(/\/+$/, '')
|
|
const result = `${cleanProxy}/${url}`
|
|
|
|
expect(result).toBe('https://electerm-mirror.html5beta.com/https://github.com/electerm/electerm/releases/download/v1.0.0/test.tar.gz')
|
|
})
|
|
|
|
test('applyProxy: handles proxy URL with trailing slash', () => {
|
|
const proxy = 'https://electerm-mirror.html5beta.com/'
|
|
const url = 'https://github.com/test/file.tar.gz'
|
|
|
|
const cleanProxy = proxy.replace(/\/+$/, '')
|
|
const result = `${cleanProxy}/${url}`
|
|
|
|
expect(result).toBe('https://electerm-mirror.html5beta.com/https://github.com/test/file.tar.gz')
|
|
})
|
|
|
|
test('formatBytes: formats bytes correctly', () => {
|
|
expect(formatBytes(0)).toBe('0 B')
|
|
expect(formatBytes(1024)).toBe('1 KB')
|
|
expect(formatBytes(1048576)).toBe('1 MB')
|
|
expect(formatBytes(1073741824)).toBe('1 GB')
|
|
})
|
|
|
|
test('formatBytes: handles partial units', () => {
|
|
expect(formatBytes(1536)).toBe('1.5 KB')
|
|
expect(formatBytes(1572864)).toBe('1.5 MB')
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Utils - Tar Extract (sync-friendly)
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Utils Tar Extract Tests ===\n')
|
|
|
|
test('extractTarGz: extracts a tar.gz file', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-tar-'))
|
|
const extDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-extract-'))
|
|
|
|
try {
|
|
const testFile = path.join(tmpDir, 'test.txt')
|
|
fs.writeFileSync(testFile, 'Hello World')
|
|
|
|
const tarFile = path.join(tmpDir, 'test.tar.gz')
|
|
await tar.create({ gzip: true, file: tarFile, cwd: tmpDir }, ['test.txt'])
|
|
|
|
await extractTarGz(tarFile, extDir)
|
|
|
|
expect(fs.existsSync(path.join(extDir, 'test.txt'))).toBe(true)
|
|
const content = fs.readFileSync(path.join(extDir, 'test.txt'), 'utf8')
|
|
expect(content).toBe('Hello World')
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true })
|
|
fs.rmSync(extDir, { recursive: true })
|
|
}
|
|
})
|
|
|
|
test('extractTarGz: strips top-level directory with strip:1', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-strip-'))
|
|
const extDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-strip-extract-'))
|
|
|
|
try {
|
|
const appDir = path.join(tmpDir, 'myapp')
|
|
fs.mkdirSync(appDir)
|
|
fs.writeFileSync(path.join(appDir, 'test.txt'), 'Stripped!')
|
|
|
|
const tarFile = path.join(tmpDir, 'test.tar.gz')
|
|
await tar.create({ gzip: true, file: tarFile, cwd: tmpDir }, ['myapp'])
|
|
|
|
await extractTarGz(tarFile, extDir, 1)
|
|
|
|
expect(fs.existsSync(path.join(extDir, 'test.txt'))).toBe(true)
|
|
expect(fs.existsSync(path.join(extDir, 'myapp'))).toBe(false)
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true })
|
|
fs.rmSync(extDir, { recursive: true })
|
|
}
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: CLI Launcher Flow
|
|
// =============================================================================
|
|
|
|
console.log('\n=== CLI Launcher Flow Tests ===\n')
|
|
|
|
test('node launcher: correct path navigation', () => {
|
|
const npmDir = path.join(_packageRoot, 'npm')
|
|
const expectedPackageRoot = path.resolve(npmDir, '..')
|
|
expect(expectedPackageRoot).toBe(_packageRoot)
|
|
})
|
|
|
|
test('node launcher: checks for electerm binary existence', () => {
|
|
const expectedBinaryPath = path.join(_packageRoot, 'electerm', 'electerm')
|
|
expect(expectedBinaryPath).toBe(getElectermExePath())
|
|
})
|
|
|
|
test('install flow: extractDir is correct', () => {
|
|
expect(_extractDir).toBe(path.join(_packageRoot, 'electerm'))
|
|
})
|
|
|
|
test('install flow: no infinite recursion', () => {
|
|
// install.js does NOT call exec('electerm')
|
|
// It only downloads and extracts
|
|
// The node launcher then spawns the extracted binary directly
|
|
const installExports = require('../../npm/install')
|
|
expect(typeof installExports.isElectermExtracted).toBe('function')
|
|
expect(typeof installExports.getElectermExePath).toBe('function')
|
|
})
|
|
|
|
test('node launcher: launches binary after install', () => {
|
|
// The Node.js launcher flow:
|
|
// 1. Check if ./electerm/electerm exists
|
|
// 2. If not: spawn node ./npm/install.js (downloads & extracts)
|
|
// 3. Spawn ./electerm/electerm (launches binary)
|
|
//
|
|
// This prevents infinite recursion because:
|
|
// - install.js never calls 'electerm' command
|
|
// - Node.js launcher uses spawn/execFile to run the binary directly
|
|
// - npm creates a proper .cmd wrapper on Windows via #!/usr/bin/env node
|
|
|
|
const bashScriptPath = path.join(_packageRoot, 'npm', 'electerm')
|
|
expect(fs.existsSync(bashScriptPath)).toBe(true)
|
|
})
|
|
|
|
// =============================================================================
|
|
// Tests: Cross-Platform Paths
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Cross-Platform Path Tests ===\n')
|
|
|
|
test('paths: packageRoot is absolute', () => {
|
|
expect(path.isAbsolute(_packageRoot)).toBe(true)
|
|
})
|
|
|
|
test('paths: extractDir is absolute', () => {
|
|
expect(path.isAbsolute(_extractDir)).toBe(true)
|
|
})
|
|
|
|
test('paths: extractDir is child of packageRoot', () => {
|
|
expect(_extractDir.startsWith(_packageRoot)).toBe(true)
|
|
})
|
|
|
|
// =============================================================================
|
|
// Async HTTP Tests (run last to avoid output interleaving)
|
|
// =============================================================================
|
|
|
|
console.log('\n=== Utils HTTP Tests (async) ===\n')
|
|
|
|
testAsync('httpGet: fetches a URL', async () => {
|
|
const result = await httpGet('https://httpbin.org/get', 10000)
|
|
expect(typeof result).toBe('string')
|
|
expect(result).toContain('httpbin.org')
|
|
})
|
|
|
|
testAsync('phin: makes HTTP request', async () => {
|
|
const result = await phin({ url: 'https://httpbin.org/get', timeout: 10000 })
|
|
expect(result.statusCode).toBe(200)
|
|
expect(Buffer.isBuffer(result.body)).toBe(true)
|
|
expect(result.body.toString()).toContain('httpbin.org')
|
|
})
|
|
|
|
testAsync('httpGet: handles redirects', async () => {
|
|
const result = await httpGet('https://httpbin.org/redirect/1', 10000)
|
|
expect(typeof result).toBe('string')
|
|
})
|
|
|
|
testAsync('httpGet: throws on 404', async () => {
|
|
try {
|
|
await httpGet('https://httpbin.org/status/404', 10000)
|
|
throw new Error('Expected to throw')
|
|
} catch (e) {
|
|
expect(e.message).toContain('404')
|
|
}
|
|
})
|
|
|
|
testAsync('download: downloads a file', async () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'electerm-test-dl-'))
|
|
try {
|
|
const result = await download(
|
|
'https://httpbin.org/robots.txt',
|
|
tmpDir,
|
|
{ extract: false }
|
|
)
|
|
expect(result.filepath).toContain('robots.txt')
|
|
expect(result.extracted).toBe(false)
|
|
expect(fs.existsSync(result.filepath)).toBe(true)
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true })
|
|
}
|
|
})
|
|
|
|
// =============================================================================
|
|
// Run all async tests and print results
|
|
// =============================================================================
|
|
|
|
async function runAsyncTests () {
|
|
for (const fn of asyncQueue) {
|
|
await fn()
|
|
}
|
|
}
|
|
|
|
async function printResults () {
|
|
await runAsyncTests()
|
|
|
|
console.log('\n========================================')
|
|
console.log(`Results: ${passed} passed, ${failed} failed`)
|
|
console.log('========================================\n')
|
|
|
|
process.exit(failed > 0 ? 1 : 0)
|
|
}
|
|
|
|
printResults().catch(err => {
|
|
console.error('Test runner error:', err)
|
|
process.exit(1)
|
|
})
|