import { join } from 'path' import { existsSync, mkdirSync, rmSync } from 'fs' import { tmpdir } from 'os' import * as fc from 'fast-check' // Minimal ConfigStore interface for testing interface IConfigStore { getBasePath(): string getTempPath(): string get(key: string): any set(key: string, value: any): void } // Mock ConfigStore for testing class MockConfigStore implements IConfigStore { private mockBasePath: string private config: Record = {} constructor() { // Create a temporary directory for testing const testDir = join(tmpdir(), 'phper-test-' + Date.now()) mkdirSync(testDir, { recursive: true }) this.mockBasePath = testDir this.config = { goVersions: [], activeGoVersion: '' } } getBasePath(): string { return this.mockBasePath } getTempPath(): string { return join(this.mockBasePath, 'temp') } get(key: string): any { return this.config[key] } set(key: string, value: any): void { this.config[key] = value } cleanup(): void { if (existsSync(this.mockBasePath)) { rmSync(this.mockBasePath, { recursive: true, force: true }) } } } // Simplified GoManager for testing (without external dependencies) class TestGoManager { private configStore: IConfigStore constructor(configStore: IConfigStore) { this.configStore = configStore } getGoBasePath(): string { return join(this.configStore.getBasePath(), 'go') } getGoPath(version: string): string { return join(this.getGoBasePath(), `go-${version}`) } getDefaultGoPath(): string { return join(this.getGoBasePath(), 'workspace') } async getInstalledVersions(): Promise { const goBasePath = this.getGoBasePath() if (!existsSync(goBasePath)) { return [] } // In a real implementation, this would scan directories // For testing, just return empty array return [] } async validateInstallation(version: string): Promise { const goPath = this.getGoPath(version) const goExe = join(goPath, 'bin', 'go.exe') return existsSync(goExe) } // Method to test API response parsing parseApiResponse(releases: any[]): any[] { const availableVersions: any[] = [] for (const release of releases) { if (release.stable) { const windowsFile = release.files?.find((f: any) => f.os === 'windows' && f.arch === 'amd64' && f.kind === 'archive' ) if (windowsFile) { availableVersions.push({ version: release.version, stable: release.stable, downloadUrl: `https://golang.org/dl/${windowsFile.filename}`, size: windowsFile.size, sha256: windowsFile.sha256 }) } } } return availableVersions.slice(0, 20) } } // Simple test runner for Node.js environment async function runTests() { console.log('Running GoManager tests...') // Basic test implementation without external framework const tests = [ { name: 'GoManager initialization', test: () => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) console.assert(manager instanceof TestGoManager, 'GoManager should initialize correctly') mockStore.cleanup() return true } }, { name: 'Path generation', test: () => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) const basePath = manager.getGoBasePath() const versionPath = manager.getGoPath('go1.21.5') const goPath = manager.getDefaultGoPath() console.assert(basePath.includes('go'), 'Base path should contain go directory') console.assert(versionPath.includes('go-go1.21.5'), 'Version path should contain version') console.assert(goPath.includes('workspace'), 'GOPATH should contain workspace') mockStore.cleanup() return true } }, { name: 'Empty versions list', test: async () => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) const versions = await manager.getInstalledVersions() console.assert(Array.isArray(versions), 'Should return array') console.assert(versions.length === 0, 'Should return empty array for no installations') mockStore.cleanup() return true } }, { name: 'Configuration integration', test: () => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) // Test config store integration const activeVersion = mockStore.get('activeGoVersion') const goVersions = mockStore.get('goVersions') console.assert(typeof activeVersion === 'string', 'Active version should be string') console.assert(Array.isArray(goVersions), 'Go versions should be array') mockStore.cleanup() return true } }, { name: 'Installation validation', test: async () => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) // Test validation for non-existent version const isValid = await manager.validateInstallation('go1.99.99') console.assert(isValid === false, 'Should return false for non-existent version') mockStore.cleanup() return true } } ] let passed = 0 let failed = 0 for (const test of tests) { try { const result = await test.test() if (result) { console.log(`✓ ${test.name}`) passed++ } else { console.log(`✗ ${test.name}`) failed++ } } catch (error) { console.log(`✗ ${test.name}: ${error}`) failed++ } } console.log(`\nTests completed: ${passed} passed, ${failed} failed`) return failed === 0 } // Run tests if this file is executed directly if (require.main === module) { runTests().then(success => { process.exit(success ? 0 : 1) }).catch(error => { console.error('Test execution failed:', error) process.exit(1) }) } // Export for potential use in other test files export { TestGoManager, MockConfigStore } // Property-based tests using Jest and fast-check describe('Go Version Management Properties', () => { test('Property 1: API Version Fetching Consistency', () => { // **Feature: go-version-management, Property 1: API Version Fetching Consistency** fc.assert(fc.property( fc.array(fc.record({ version: fc.string({ minLength: 1 }), stable: fc.boolean(), files: fc.array(fc.record({ os: fc.constantFrom('windows', 'linux', 'darwin'), arch: fc.constantFrom('amd64', '386', 'arm64'), kind: fc.constantFrom('archive', 'installer', 'source'), filename: fc.string({ minLength: 1 }), size: fc.nat(), sha256: fc.string({ minLength: 64, maxLength: 64 }).filter(s => /^[0-9a-f]+$/i.test(s)) })) })), (mockApiResponse) => { const mockStore = new MockConfigStore() const manager = new TestGoManager(mockStore) try { const result = manager.parseApiResponse(mockApiResponse) // Verify all returned versions have required fields result.forEach(version => { expect(version).toHaveProperty('version') expect(version).toHaveProperty('stable') expect(version).toHaveProperty('downloadUrl') expect(version).toHaveProperty('size') expect(version).toHaveProperty('sha256') // Verify types expect(typeof version.version).toBe('string') expect(typeof version.stable).toBe('boolean') expect(typeof version.downloadUrl).toBe('string') expect(typeof version.size).toBe('number') expect(typeof version.sha256).toBe('string') // Verify download URL format expect(version.downloadUrl).toMatch(/^https:\/\/golang\.org\/dl\//) }) // Verify only stable versions are returned result.forEach(version => { expect(version.stable).toBe(true) }) // Verify result is limited to 20 versions expect(result.length).toBeLessThanOrEqual(20) return true } finally { mockStore.cleanup() } } ), { numRuns: 10 }) }) test('Property 2: Download URL Construction', () => { // **Feature: go-version-management, Property 2: Download URL Construction** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), filename: fc.string({ minLength: 1 }).filter(f => f.endsWith('.zip')), size: fc.nat({ min: 1 }), sha256: fc.string({ minLength: 64, maxLength: 64 }).filter(s => /^[0-9a-f]+$/i.test(s)) }), (goVersion) => { const mockStore = new MockConfigStore() try { // Simulate constructing download URL const downloadUrl = `https://golang.org/dl/${goVersion.filename}` // Verify URL construction expect(downloadUrl).toMatch(/^https:\/\/golang\.org\/dl\//) expect(downloadUrl).toContain(goVersion.filename) expect(downloadUrl).toMatch(/\.zip$/) // Verify the URL is well-formed expect(() => new URL(downloadUrl)).not.toThrow() // Verify version information is preserved expect(goVersion.version).toMatch(/^go\d+\.\d+\.\d+$/) expect(goVersion.size).toBeGreaterThan(0) expect(goVersion.sha256).toMatch(/^[0-9a-f]{64}$/i) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 3: Download Progress Reporting', () => { // **Feature: go-version-management, Property 3: Download Progress Reporting** fc.assert(fc.property( fc.record({ downloadedBytes: fc.nat(), totalBytes: fc.nat({ min: 1 }), percent: fc.integer({ min: 0, max: 100 }) }).filter(progress => progress.downloadedBytes <= progress.totalBytes), (progressData) => { const mockStore = new MockConfigStore() try { // Simulate progress reporting structure const progress = { percent: progressData.percent, downloadedBytes: progressData.downloadedBytes, totalBytes: progressData.totalBytes, speed: progressData.downloadedBytes > 0 ? progressData.downloadedBytes / 1000 : 0 } // Verify progress data structure expect(progress).toHaveProperty('percent') expect(progress).toHaveProperty('downloadedBytes') expect(progress).toHaveProperty('totalBytes') expect(progress).toHaveProperty('speed') // Verify data types expect(typeof progress.percent).toBe('number') expect(typeof progress.downloadedBytes).toBe('number') expect(typeof progress.totalBytes).toBe('number') expect(typeof progress.speed).toBe('number') // Verify value ranges expect(progress.percent).toBeGreaterThanOrEqual(0) expect(progress.percent).toBeLessThanOrEqual(100) expect(progress.downloadedBytes).toBeGreaterThanOrEqual(0) expect(progress.totalBytes).toBeGreaterThan(0) expect(progress.downloadedBytes).toBeLessThanOrEqual(progress.totalBytes) expect(progress.speed).toBeGreaterThanOrEqual(0) // Verify percentage calculation consistency if (progress.totalBytes > 0) { const calculatedPercent = Math.round((progress.downloadedBytes / progress.totalBytes) * 100) // Allow some tolerance for rounding differences expect(Math.abs(progress.percent - calculatedPercent)).toBeLessThanOrEqual(1) } return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 4: Installation Validation Completeness', () => { // **Feature: go-version-management, Property 4: Installation Validation Completeness** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), hasGoExe: fc.boolean(), hasGofmtExe: fc.boolean(), hasValidVersion: fc.boolean(), hasSrcDir: fc.boolean() }), (installationData) => { const mockStore = new MockConfigStore() try { // Simulate installation validation components const validationChecks = { goExeExists: installationData.hasGoExe, gofmtExeExists: installationData.hasGofmtExe, versionMatches: installationData.hasValidVersion, srcDirExists: installationData.hasSrcDir } // For a complete installation, all checks should pass const isCompleteInstallation = Object.values(validationChecks).every(check => check === true) // Verify validation structure expect(validationChecks).toHaveProperty('goExeExists') expect(validationChecks).toHaveProperty('gofmtExeExists') expect(validationChecks).toHaveProperty('versionMatches') expect(validationChecks).toHaveProperty('srcDirExists') // Verify types expect(typeof validationChecks.goExeExists).toBe('boolean') expect(typeof validationChecks.gofmtExeExists).toBe('boolean') expect(typeof validationChecks.versionMatches).toBe('boolean') expect(typeof validationChecks.srcDirExists).toBe('boolean') // Verify version format expect(installationData.version).toMatch(/^go\d+\.\d+\.\d+$/) // If installation is complete, all validation checks should pass if (isCompleteInstallation) { expect(validationChecks.goExeExists).toBe(true) expect(validationChecks.gofmtExeExists).toBe(true) expect(validationChecks.versionMatches).toBe(true) expect(validationChecks.srcDirExists).toBe(true) } // If any critical component is missing, installation should be considered incomplete if (!validationChecks.goExeExists || !validationChecks.gofmtExeExists) { expect(isCompleteInstallation).toBe(false) } return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 5: Duplicate Installation Prevention', () => { // **Feature: go-version-management, Property 5: Duplicate Installation Prevention** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), isAlreadyInstalled: fc.boolean(), installationAttempts: fc.integer({ min: 1, max: 5 }) }), (installationData) => { const mockStore = new MockConfigStore() try { // Simulate installation state const installedVersions = installationData.isAlreadyInstalled ? [installationData.version] : [] // Simulate multiple installation attempts const installationResults = [] for (let i = 0; i < installationData.installationAttempts; i++) { const isVersionInstalled = installedVersions.includes(installationData.version) const result = { success: !isVersionInstalled, message: isVersionInstalled ? `Go ${installationData.version} 已安装,无需重复安装` : `Go ${installationData.version} 安装成功`, attempt: i + 1 } installationResults.push(result) // If installation was successful, add to installed versions if (result.success) { installedVersions.push(installationData.version) } } // Verify installation results structure installationResults.forEach(result => { expect(result).toHaveProperty('success') expect(result).toHaveProperty('message') expect(result).toHaveProperty('attempt') expect(typeof result.success).toBe('boolean') expect(typeof result.message).toBe('string') expect(typeof result.attempt).toBe('number') expect(result.message.length).toBeGreaterThan(0) }) // Verify duplicate installation prevention if (installationData.isAlreadyInstalled) { // If version was already installed, all attempts should fail installationResults.forEach(result => { expect(result.success).toBe(false) expect(result.message).toContain('已安装') }) } else { // If version wasn't installed, first attempt should succeed, rest should fail expect(installationResults[0].success).toBe(true) expect(installationResults[0].message).toContain('安装成功') // Subsequent attempts should fail due to duplicate prevention for (let i = 1; i < installationResults.length; i++) { expect(installationResults[i].success).toBe(false) expect(installationResults[i].message).toContain('已安装') } } // Verify version should only appear once in installed versions list const versionCount = installedVersions.filter(v => v === installationData.version).length expect(versionCount).toBeLessThanOrEqual(1) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 11: Environment Variable Validation', () => { // **Feature: go-version-management, Property 11: Environment Variable Validation** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), goroot: fc.string({ minLength: 1 }), gopath: fc.string({ minLength: 1 }), goBin: fc.string({ minLength: 1 }), systemGoVersionOutput: fc.string({ minLength: 1 }), systemGoRootOutput: fc.string({ minLength: 1 }) }), (validationData) => { const mockStore = new MockConfigStore() try { // Simulate environment variable validation after version switch const expectedGoRoot = validationData.goroot const expectedGoPath = validationData.gopath const expectedGoBin = validationData.goBin // Simulate system command outputs const goVersionMatches = validationData.systemGoVersionOutput.includes(validationData.version) const goRootMatches = validationData.systemGoRootOutput.trim() === expectedGoRoot // Simulate validation results const validationResult = { environmentVariables: { GOROOT: expectedGoRoot, GOPATH: expectedGoPath, PATH: `${expectedGoBin};C:\\Windows\\System32` }, systemValidation: { goVersionOutput: validationData.systemGoVersionOutput, goRootOutput: validationData.systemGoRootOutput, goVersionMatches, goRootMatches }, errors: [] as string[] } // Perform validation checks if (!goVersionMatches) { validationResult.errors.push(`go version 输出不匹配: 期望包含 '${validationData.version}', 实际 '${validationData.systemGoVersionOutput}'`) } if (!goRootMatches) { validationResult.errors.push(`GOROOT 不匹配: 期望 '${expectedGoRoot}', 实际 '${validationData.systemGoRootOutput.trim()}'`) } // Check if PATH contains Go binary const pathContainsGoBin = validationResult.environmentVariables.PATH.includes(expectedGoBin) if (!pathContainsGoBin) { validationResult.errors.push(`PATH 中缺少 Go 二进制路径: '${expectedGoBin}'`) } const isValid = validationResult.errors.length === 0 // Verify validation result structure expect(validationResult).toHaveProperty('environmentVariables') expect(validationResult).toHaveProperty('systemValidation') expect(validationResult).toHaveProperty('errors') // Verify environment variables structure expect(validationResult.environmentVariables).toHaveProperty('GOROOT') expect(validationResult.environmentVariables).toHaveProperty('GOPATH') expect(validationResult.environmentVariables).toHaveProperty('PATH') // Verify system validation structure expect(validationResult.systemValidation).toHaveProperty('goVersionOutput') expect(validationResult.systemValidation).toHaveProperty('goRootOutput') expect(validationResult.systemValidation).toHaveProperty('goVersionMatches') expect(validationResult.systemValidation).toHaveProperty('goRootMatches') // Verify types expect(typeof validationResult.environmentVariables.GOROOT).toBe('string') expect(typeof validationResult.environmentVariables.GOPATH).toBe('string') expect(typeof validationResult.environmentVariables.PATH).toBe('string') expect(typeof validationResult.systemValidation.goVersionMatches).toBe('boolean') expect(typeof validationResult.systemValidation.goRootMatches).toBe('boolean') expect(Array.isArray(validationResult.errors)).toBe(true) // Verify validation logic if (goVersionMatches && goRootMatches && pathContainsGoBin) { expect(isValid).toBe(true) expect(validationResult.errors.length).toBe(0) } else { expect(isValid).toBe(false) expect(validationResult.errors.length).toBeGreaterThan(0) } // Verify error messages are descriptive validationResult.errors.forEach(error => { expect(typeof error).toBe('string') expect(error.length).toBeGreaterThan(0) expect(error).toMatch(/期望|实际|不匹配|缺少/) }) // Verify PATH structure const pathEntries = validationResult.environmentVariables.PATH.split(';') expect(pathEntries.length).toBeGreaterThan(0) if (pathContainsGoBin) { expect(pathEntries).toContain(expectedGoBin) } // Verify version format in validation expect(validationData.version).toMatch(/^go\d+\.\d+\.\d+$/) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 7: Active Version Environment Management', () => { // **Feature: go-version-management, Property 7: Active Version Environment Management** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), goroot: fc.string({ minLength: 1 }), gopath: fc.string({ minLength: 1 }), goBin: fc.string({ minLength: 1 }), existingPathEntries: fc.array(fc.string({ minLength: 1 })), hasOldGoPaths: fc.boolean() }), (envData) => { const mockStore = new MockConfigStore() try { // Simulate environment variable update process const currentPath = envData.existingPathEntries.join(';') // Add some old Go paths if specified let pathWithOldGo = currentPath if (envData.hasOldGoPaths) { pathWithOldGo = `${currentPath};C:\\go\\go-1.20.0\\bin;C:\\go\\go-1.19.0\\bin` } // Simulate PATH cleaning and updating const pathArray = pathWithOldGo.split(';').filter(p => p.trim() !== '') // Remove old Go paths (simulate the cleaning logic) const filteredPaths = pathArray.filter(p => !p.includes('\\go\\go-') && !p.includes('\\go-') && !p.includes('\\golang\\') && !p.includes('\\Go\\') ) // Add new Go path const newPathArray = [envData.goBin, ...filteredPaths] const finalPath = newPathArray.filter(p => p.trim() !== '').join(';') // Simulate environment variables const environmentVariables = { GOROOT: envData.goroot, GOPATH: envData.gopath, PATH: finalPath } // Verify environment variable structure expect(environmentVariables).toHaveProperty('GOROOT') expect(environmentVariables).toHaveProperty('GOPATH') expect(environmentVariables).toHaveProperty('PATH') // Verify types expect(typeof environmentVariables.GOROOT).toBe('string') expect(typeof environmentVariables.GOPATH).toBe('string') expect(typeof environmentVariables.PATH).toBe('string') // Verify values are non-empty expect(environmentVariables.GOROOT.length).toBeGreaterThan(0) expect(environmentVariables.GOPATH.length).toBeGreaterThan(0) expect(environmentVariables.PATH.length).toBeGreaterThan(0) // Verify PATH contains the new Go binary path expect(environmentVariables.PATH).toContain(envData.goBin) // Verify old Go paths are removed const pathEntries = environmentVariables.PATH.split(';') const hasOldGoPaths = pathEntries.some(p => (p.includes('\\go\\go-') || p.includes('\\go-')) && p !== envData.goBin ) expect(hasOldGoPaths).toBe(false) // Verify Go binary path appears only once const goPathCount = pathEntries.filter(p => p === envData.goBin).length expect(goPathCount).toBe(1) // Verify Go binary path is at the beginning (highest priority) expect(pathEntries[0]).toBe(envData.goBin) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 14: GOPATH Default Configuration', () => { // **Feature: go-version-management, Property 14: GOPATH Default Configuration** fc.assert(fc.property( fc.record({ basePath: fc.string({ minLength: 1 }), version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)) }), (configData) => { const mockStore = new MockConfigStore() try { // Simulate GOPATH configuration logic const goBasePath = `${configData.basePath}\\go` const defaultGoPath = `${goBasePath}\\workspace` // Simulate GOPATH workspace structure const workspaceStructure = { gopath: defaultGoPath, subdirectories: ['src', 'pkg', 'bin'], fullPaths: [ `${defaultGoPath}\\src`, `${defaultGoPath}\\pkg`, `${defaultGoPath}\\bin` ] } // Verify GOPATH structure expect(workspaceStructure).toHaveProperty('gopath') expect(workspaceStructure).toHaveProperty('subdirectories') expect(workspaceStructure).toHaveProperty('fullPaths') // Verify types expect(typeof workspaceStructure.gopath).toBe('string') expect(Array.isArray(workspaceStructure.subdirectories)).toBe(true) expect(Array.isArray(workspaceStructure.fullPaths)).toBe(true) // Verify GOPATH format expect(workspaceStructure.gopath).toContain('workspace') expect(workspaceStructure.gopath.length).toBeGreaterThan(0) // Verify standard Go workspace subdirectories expect(workspaceStructure.subdirectories).toContain('src') expect(workspaceStructure.subdirectories).toContain('pkg') expect(workspaceStructure.subdirectories).toContain('bin') expect(workspaceStructure.subdirectories.length).toBe(3) // Verify full paths are correctly constructed workspaceStructure.fullPaths.forEach(path => { expect(path).toContain(workspaceStructure.gopath) expect(typeof path).toBe('string') expect(path.length).toBeGreaterThan(workspaceStructure.gopath.length) }) // Verify each subdirectory has a corresponding full path workspaceStructure.subdirectories.forEach(subdir => { const expectedPath = `${workspaceStructure.gopath}\\${subdir}` expect(workspaceStructure.fullPaths).toContain(expectedPath) }) // Verify GOPATH is under the Go base directory expect(workspaceStructure.gopath).toContain(goBasePath) // Verify GOPATH uses the standard workspace directory name expect(workspaceStructure.gopath.endsWith('workspace')).toBe(true) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 6: File System Cleanup on Uninstall', () => { // **Feature: go-version-management, Property 6: File System Cleanup on Uninstall** fc.assert(fc.property( fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), versionDir: fc.string({ minLength: 1 }), fileCount: fc.nat({ min: 1, max: 100 }), dirCount: fc.nat({ min: 1, max: 20 }), totalSize: fc.nat({ min: 1000, max: 1000000 }), uninstallSuccess: fc.boolean() }), (uninstallData) => { const mockStore = new MockConfigStore() try { // Simulate uninstall operation and file system cleanup const preUninstallState = { versionExists: true, directoryPath: uninstallData.versionDir, fileCount: uninstallData.fileCount, dirCount: uninstallData.dirCount, totalSize: uninstallData.totalSize } // Simulate uninstall result const uninstallResult = { success: uninstallData.uninstallSuccess, message: uninstallData.uninstallSuccess ? `Go ${uninstallData.version} 已成功卸载` : `卸载失败: 文件系统清理失败`, filesRemoved: uninstallData.uninstallSuccess ? uninstallData.fileCount : 0, dirsRemoved: uninstallData.uninstallSuccess ? uninstallData.dirCount : 0, bytesFreed: uninstallData.uninstallSuccess ? uninstallData.totalSize : 0 } // Simulate post-uninstall state const postUninstallState = { versionExists: !uninstallData.uninstallSuccess, directoryPath: uninstallData.versionDir, fileCount: uninstallData.uninstallSuccess ? 0 : uninstallData.fileCount, dirCount: uninstallData.uninstallSuccess ? 0 : uninstallData.dirCount, totalSize: uninstallData.uninstallSuccess ? 0 : uninstallData.totalSize } // Verify uninstall result structure expect(uninstallResult).toHaveProperty('success') expect(uninstallResult).toHaveProperty('message') expect(uninstallResult).toHaveProperty('filesRemoved') expect(uninstallResult).toHaveProperty('dirsRemoved') expect(uninstallResult).toHaveProperty('bytesFreed') // Verify types expect(typeof uninstallResult.success).toBe('boolean') expect(typeof uninstallResult.message).toBe('string') expect(typeof uninstallResult.filesRemoved).toBe('number') expect(typeof uninstallResult.dirsRemoved).toBe('number') expect(typeof uninstallResult.bytesFreed).toBe('number') // Verify message is descriptive expect(uninstallResult.message.length).toBeGreaterThan(0) expect(uninstallResult.message).toContain(uninstallData.version) // Verify cleanup completeness for successful uninstall if (uninstallData.uninstallSuccess) { expect(uninstallResult.success).toBe(true) expect(uninstallResult.message).toContain('成功卸载') expect(uninstallResult.filesRemoved).toBe(preUninstallState.fileCount) expect(uninstallResult.dirsRemoved).toBe(preUninstallState.dirCount) expect(uninstallResult.bytesFreed).toBe(preUninstallState.totalSize) // Post-uninstall state should show complete cleanup expect(postUninstallState.versionExists).toBe(false) expect(postUninstallState.fileCount).toBe(0) expect(postUninstallState.dirCount).toBe(0) expect(postUninstallState.totalSize).toBe(0) } else { expect(uninstallResult.success).toBe(false) expect(uninstallResult.message).toContain('卸载失败') expect(uninstallResult.filesRemoved).toBe(0) expect(uninstallResult.dirsRemoved).toBe(0) expect(uninstallResult.bytesFreed).toBe(0) // Post-uninstall state should show no changes expect(postUninstallState.versionExists).toBe(true) expect(postUninstallState.fileCount).toBe(preUninstallState.fileCount) expect(postUninstallState.dirCount).toBe(preUninstallState.dirCount) expect(postUninstallState.totalSize).toBe(preUninstallState.totalSize) } // Verify numeric values are non-negative expect(uninstallResult.filesRemoved).toBeGreaterThanOrEqual(0) expect(uninstallResult.dirsRemoved).toBeGreaterThanOrEqual(0) expect(uninstallResult.bytesFreed).toBeGreaterThanOrEqual(0) // Verify version format expect(uninstallData.version).toMatch(/^go\d+\.\d+\.\d+$/) return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 8: Version State Consistency', () => { // **Feature: go-version-management, Property 8: Version State Consistency** fc.assert(fc.property( fc.record({ initialVersions: fc.array(fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v))), versionToUninstall: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), wasActiveVersion: fc.boolean(), uninstallSuccess: fc.boolean() }), (stateData) => { const mockStore = new MockConfigStore() try { // Simulate initial state const initialState = { installedVersions: [...stateData.initialVersions], activeVersion: stateData.wasActiveVersion ? stateData.versionToUninstall : '', versionExists: stateData.initialVersions.includes(stateData.versionToUninstall) } // Simulate uninstall operation const uninstallOperation = { targetVersion: stateData.versionToUninstall, wasActive: stateData.wasActiveVersion && initialState.versionExists, success: stateData.uninstallSuccess && initialState.versionExists } // Simulate post-uninstall state const postUninstallState = { installedVersions: uninstallOperation.success ? initialState.installedVersions.filter(v => v !== stateData.versionToUninstall) : [...initialState.installedVersions], activeVersion: (uninstallOperation.success && uninstallOperation.wasActive) ? '' : initialState.activeVersion, versionExists: !uninstallOperation.success && initialState.versionExists } // Verify state consistency expect(initialState).toHaveProperty('installedVersions') expect(initialState).toHaveProperty('activeVersion') expect(initialState).toHaveProperty('versionExists') expect(postUninstallState).toHaveProperty('installedVersions') expect(postUninstallState).toHaveProperty('activeVersion') expect(postUninstallState).toHaveProperty('versionExists') // Verify types expect(Array.isArray(initialState.installedVersions)).toBe(true) expect(Array.isArray(postUninstallState.installedVersions)).toBe(true) expect(typeof initialState.activeVersion).toBe('string') expect(typeof postUninstallState.activeVersion).toBe('string') expect(typeof initialState.versionExists).toBe('boolean') expect(typeof postUninstallState.versionExists).toBe('boolean') // Verify version format expect(stateData.versionToUninstall).toMatch(/^go\d+\.\d+\.\d+$/) initialState.installedVersions.forEach(version => { expect(version).toMatch(/^go\d+\.\d+\.\d+$/) }) postUninstallState.installedVersions.forEach(version => { expect(version).toMatch(/^go\d+\.\d+\.\d+$/) }) // Verify state transitions for successful uninstall if (uninstallOperation.success) { // Version should be removed from installed versions list expect(postUninstallState.installedVersions).not.toContain(stateData.versionToUninstall) expect(postUninstallState.installedVersions.length).toBe( Math.max(0, initialState.installedVersions.length - 1) ) // If uninstalled version was active, active version should be cleared if (uninstallOperation.wasActive) { expect(postUninstallState.activeVersion).toBe('') } // Version should no longer exist expect(postUninstallState.versionExists).toBe(false) } else { // State should remain unchanged for failed uninstall expect(postUninstallState.installedVersions).toEqual(initialState.installedVersions) expect(postUninstallState.activeVersion).toBe(initialState.activeVersion) expect(postUninstallState.versionExists).toBe(initialState.versionExists) } // Verify active version consistency if (postUninstallState.activeVersion !== '') { expect(postUninstallState.installedVersions).toContain(postUninstallState.activeVersion) } // Verify no duplicate versions in installed list const uniqueVersions = [...new Set(postUninstallState.installedVersions)] expect(uniqueVersions.length).toBe(postUninstallState.installedVersions.length) // Verify uninstall operation properties expect(typeof uninstallOperation.success).toBe('boolean') expect(typeof uninstallOperation.wasActive).toBe('boolean') // If version didn't exist initially, uninstall should fail if (!initialState.versionExists) { expect(uninstallOperation.success).toBe(false) } return true } finally { mockStore.cleanup() } } ), { numRuns: 100 }) }) test('Property 9: Version Information Completeness', () => { // **Feature: go-version-management, Property 9: Version Information Completeness** fc.assert(fc.property( fc.array(fc.record({ version: fc.string({ minLength: 1 }).filter(v => /^go\d+\.\d+\.\d+$/.test(v)), path: fc.string({ minLength: 1 }), isActive: fc.boolean(), goroot: fc.string({ minLength: 1 }), gopath: fc.string({ minLength: 1 }) })), (mockVersions) => { const mockStore = new MockConfigStore() try { // Verify all version objects have required fields mockVersions.forEach(version => { expect(version).toHaveProperty('version') expect(version).toHaveProperty('path') expect(version).toHaveProperty('isActive') expect(version).toHaveProperty('goroot') expect(version).toHaveProperty('gopath') // Verify types expect(typeof version.version).toBe('string') expect(typeof version.path).toBe('string') expect(typeof version.isActive).toBe('boolean') expect(typeof version.goroot).toBe('string') expect(typeof version.gopath).toBe('string') // Verify version format expect(version.version).toMatch(/^go\d+\.\d+\.\d+$/) // Verify paths are non-empty expect(version.path.length).toBeGreaterThan(0) expect(version.goroot.length).toBeGreaterThan(0) expect(version.gopath.length).toBeGreaterThan(0) }) // Verify at most one version can be active const activeVersions = mockVersions.filter(v => v.isActive) expect(activeVersions.length).toBeLessThanOrEqual(1) return true } finally { mockStore.cleanup() } } ), { numRuns: 10 }) }) })