UNPKG

bumpx-action

Version:

GitHub Action for bumpx version bumping tool.

470 lines (404 loc) 17.4 kB
import { describe, expect, it } from 'bun:test' import { ActionError, ActionErrorType } from '../src/types' describe('GitHub Action', () => { describe('ActionError class', () => { it('should create ActionError with message and type', () => { const error = new ActionError( 'Test error message', ActionErrorType.BUN_INSTALLATION_FAILED, ) expect(error.message).toBe('Test error message') expect(error.type).toBe(ActionErrorType.BUN_INSTALLATION_FAILED) expect(error.name).toBe('ActionError') expect(error.details).toBeUndefined() }) it('should create ActionError with details', () => { const details = { key: 'value', count: 42 } const error = new ActionError( 'Detailed error', ActionErrorType.CONFIG_PARSING_FAILED, details, ) expect(error.message).toBe('Detailed error') expect(error.type).toBe(ActionErrorType.CONFIG_PARSING_FAILED) expect(error.details).toEqual(details) }) it('should extend Error correctly', () => { const error = new ActionError( 'Test error', ActionErrorType.NETWORK_ERROR, ) expect(error instanceof Error).toBe(true) expect(error instanceof ActionError).toBe(true) }) }) describe('ActionErrorType enum', () => { it('should have all expected error types', () => { expect(ActionErrorType.BUN_INSTALLATION_FAILED).toBe(ActionErrorType.BUN_INSTALLATION_FAILED) expect(ActionErrorType.BUMPX_INSTALLATION_FAILED).toBe(ActionErrorType.BUMPX_INSTALLATION_FAILED) expect(ActionErrorType.PKGX_INSTALLATION_FAILED).toBe(ActionErrorType.PKGX_INSTALLATION_FAILED) expect(ActionErrorType.PACKAGE_INSTALLATION_FAILED).toBe(ActionErrorType.PACKAGE_INSTALLATION_FAILED) expect(ActionErrorType.DEPENDENCY_DETECTION_FAILED).toBe(ActionErrorType.DEPENDENCY_DETECTION_FAILED) expect(ActionErrorType.CONFIG_PARSING_FAILED).toBe(ActionErrorType.CONFIG_PARSING_FAILED) expect(ActionErrorType.TIMEOUT_EXCEEDED).toBe(ActionErrorType.TIMEOUT_EXCEEDED) expect(ActionErrorType.UNSUPPORTED_PLATFORM).toBe(ActionErrorType.UNSUPPORTED_PLATFORM) expect(ActionErrorType.INSUFFICIENT_PERMISSIONS).toBe(ActionErrorType.INSUFFICIENT_PERMISSIONS) expect(ActionErrorType.NETWORK_ERROR).toBe(ActionErrorType.NETWORK_ERROR) }) it('should have string values for all error types', () => { const errorTypes = Object.values(ActionErrorType) errorTypes.forEach((type) => { expect(typeof type).toBe('string') expect(type.length).toBeGreaterThan(0) }) }) }) describe('Interface validation', () => { it('should validate ActionInputs interface structure', () => { const validInputs = { packages: 'typescript eslint', configPath: 'bumpx.config.ts', bumpxVersion: 'latest', installBun: true, installPkgx: true, verbose: false, skipDetection: false, workingDirectory: '.', envVars: '{}', timeout: 600, cache: true, cacheKey: 'bumpx-packages', } // Type validation - these should compile without errors expect(typeof validInputs.packages).toBe('string') expect(typeof validInputs.configPath).toBe('string') expect(typeof validInputs.bumpxVersion).toBe('string') expect(typeof validInputs.installBun).toBe('boolean') expect(typeof validInputs.installPkgx).toBe('boolean') expect(typeof validInputs.verbose).toBe('boolean') expect(typeof validInputs.skipDetection).toBe('boolean') expect(typeof validInputs.workingDirectory).toBe('string') expect(typeof validInputs.envVars).toBe('string') expect(typeof validInputs.timeout).toBe('number') expect(typeof validInputs.cache).toBe('boolean') expect(typeof validInputs.cacheKey).toBe('string') }) it('should validate PackageInstallResult interface structure', () => { const successResult = { name: 'typescript', success: true, installTime: 1500, version: '5.0.0', } const failureResult = { name: 'bad-package', success: false, installTime: 500, error: 'Package not found', } expect(successResult.name).toBe('typescript') expect(successResult.success).toBe(true) expect(successResult.installTime).toBe(1500) expect(successResult.version).toBe('5.0.0') expect(failureResult.name).toBe('bad-package') expect(failureResult.success).toBe(false) expect(failureResult.installTime).toBe(500) expect(failureResult.error).toBe('Package not found') }) it('should validate InstallationSummary interface structure', () => { const summary = { totalPackages: 3, successfulInstalls: 2, failedInstalls: 1, results: [], totalTime: 5000, bumpxInstalled: true, bunInstalled: true, pkgxInstalled: false, } expect(summary.totalPackages).toBe(3) expect(summary.successfulInstalls).toBe(2) expect(summary.failedInstalls).toBe(1) expect(Array.isArray(summary.results)).toBe(true) expect(summary.totalTime).toBe(5000) expect(summary.bumpxInstalled).toBe(true) expect(summary.bunInstalled).toBe(true) expect(summary.pkgxInstalled).toBe(false) }) }) describe('Input validation logic', () => { it('should handle timeout validation correctly', () => { const validTimeouts = [1, 300, 600, 1800, 3600] const invalidTimeouts = [0, -1, 3601, 5000] validTimeouts.forEach((timeout) => { expect(timeout > 0 && timeout <= 3600).toBe(true) }) invalidTimeouts.forEach((timeout) => { expect(timeout > 0 && timeout <= 3600).toBe(false) }) }) it('should validate environment variables JSON parsing', () => { const validJson = '{"NODE_ENV":"test","DEBUG":"true"}' const invalidJson = 'invalid json string' expect(() => JSON.parse(validJson)).not.toThrow() expect(() => JSON.parse(invalidJson)).toThrow() const parsed = JSON.parse(validJson) expect(parsed.NODE_ENV).toBe('test') expect(parsed.DEBUG).toBe('true') }) it('should handle package list parsing', () => { const packages = 'typescript eslint prettier' const packageList = packages.split(/\s+/).filter(Boolean) expect(packageList).toEqual(['typescript', 'eslint', 'prettier']) expect(packageList.length).toBe(3) // Test empty input const emptyPackages = '' const emptyList = emptyPackages.split(/\s+/).filter(Boolean) expect(emptyList).toEqual([]) }) }) describe('Platform detection', () => { it('should identify supported platforms', () => { const supportedPlatforms = ['darwin', 'linux', 'win32'] const unsupportedPlatforms = ['aix', 'freebsd', 'openbsd', 'sunos'] supportedPlatforms.forEach((platform) => { expect(['darwin', 'linux', 'win32'].includes(platform)).toBe(true) }) unsupportedPlatforms.forEach((platform) => { expect(['darwin', 'linux', 'win32'].includes(platform)).toBe(false) }) }) it('should determine correct installation commands by platform', () => { const getInstallCommand = (platform: string) => { if (platform === 'darwin' || platform === 'linux') { return ['curl', ['-fsSL', 'https://bun.sh/install', '|', 'bash']] } else if (platform === 'win32') { return ['powershell', ['-Command', 'irm bun.sh/install.ps1 | iex']] } else { throw new Error(`Unsupported platform: ${platform}`) } } expect(getInstallCommand('linux')).toEqual(['curl', ['-fsSL', 'https://bun.sh/install', '|', 'bash']]) expect(getInstallCommand('darwin')).toEqual(['curl', ['-fsSL', 'https://bun.sh/install', '|', 'bash']]) expect(getInstallCommand('win32')).toEqual(['powershell', ['-Command', 'irm bun.sh/install.ps1 | iex']]) expect(() => getInstallCommand('aix')).toThrow('Unsupported platform: aix') }) }) describe('Version handling', () => { it('should handle version specifications correctly', () => { const getPackageSpec = (version: string) => { return version === 'latest' ? 'bumpx' : `bumpx@${version}` } expect(getPackageSpec('latest')).toBe('bumpx') expect(getPackageSpec('0.1.0')).toBe('bumpx@0.1.0') expect(getPackageSpec('1.2.3-beta.1')).toBe('bumpx@1.2.3-beta.1') }) it('should parse version output correctly', () => { const parseVersion = (output: string) => output.trim() expect(parseVersion('1.0.0\n')).toBe('1.0.0') expect(parseVersion(' 2.1.3 ')).toBe('2.1.3') expect(parseVersion('0.1.0-beta.1')).toBe('0.1.0-beta.1') }) }) describe('Error handling patterns', () => { it('should categorize errors correctly', () => { const categorizeError = (error: Error) => { const message = error.message.toLowerCase() if (message.includes('timeout')) { return ActionErrorType.TIMEOUT_EXCEEDED } else if (message.includes('network')) { return ActionErrorType.NETWORK_ERROR } else if (message.includes('permission')) { return ActionErrorType.INSUFFICIENT_PERMISSIONS } else if (message.includes('platform')) { return ActionErrorType.UNSUPPORTED_PLATFORM } else { return ActionErrorType.PACKAGE_INSTALLATION_FAILED } } expect(categorizeError(new Error('Connection timeout'))).toBe(ActionErrorType.TIMEOUT_EXCEEDED) expect(categorizeError(new Error('Network unreachable'))).toBe(ActionErrorType.NETWORK_ERROR) expect(categorizeError(new Error('Permission denied'))).toBe(ActionErrorType.INSUFFICIENT_PERMISSIONS) expect(categorizeError(new Error('Unsupported platform'))).toBe(ActionErrorType.UNSUPPORTED_PLATFORM) expect(categorizeError(new Error('Package not found'))).toBe(ActionErrorType.PACKAGE_INSTALLATION_FAILED) }) it('should handle installation results correctly', () => { const processResults = (results: Array<{ success: boolean, name: string, error?: string }>) => { const successful = results.filter(r => r.success) const failed = results.filter(r => !r.success) return { successCount: successful.length, failureCount: failed.length, successfulPackages: successful.map(r => r.name), failedPackages: failed.map(r => ({ name: r.name, error: r.error })), } } const mockResults = [ { success: true, name: 'typescript' }, { success: false, name: 'bad-package', error: 'Not found' }, { success: true, name: 'eslint' }, ] const processed = processResults(mockResults) expect(processed.successCount).toBe(2) expect(processed.failureCount).toBe(1) expect(processed.successfulPackages).toEqual(['typescript', 'eslint']) expect(processed.failedPackages).toEqual([{ name: 'bad-package', error: 'Not found' }]) }) }) describe('Output formatting', () => { it('should format outputs correctly', () => { const formatOutputs = (summary: any, packages: string[]) => { return { success: 'true', packagesInstalled: String(summary.successfulInstalls), installedPackages: JSON.stringify(packages), summary: JSON.stringify(summary), } } const testSummary = { successfulInstalls: 2, failedInstalls: 1, totalTime: 5000, } const outputs = formatOutputs(testSummary, ['typescript', 'eslint']) expect(outputs.success).toBe('true') expect(outputs.packagesInstalled).toBe('2') expect(JSON.parse(outputs.installedPackages)).toEqual(['typescript', 'eslint']) expect(JSON.parse(outputs.summary)).toEqual(testSummary) }) it('should format summary logs correctly', () => { const formatSummary = (summary: any) => { const lines = [ '='.repeat(50), 'Installation Summary', '='.repeat(50), `Total packages: ${summary.totalPackages}`, `Successful installations: ${summary.successfulInstalls}`, `Failed installations: ${summary.failedInstalls}`, `Total time: ${summary.totalTime}ms`, `bumpx installed: ${summary.bumpxInstalled}`, `Bun installed: ${summary.bunInstalled}`, `pkgx installed: ${summary.pkgxInstalled}`, '='.repeat(50), ] return lines } const testSummary = { totalPackages: 3, successfulInstalls: 2, failedInstalls: 1, totalTime: 5000, bumpxInstalled: true, bunInstalled: true, pkgxInstalled: false, } const formatted = formatSummary(testSummary) expect(formatted).toContain('Installation Summary') expect(formatted).toContain('Total packages: 3') expect(formatted).toContain('Successful installations: 2') expect(formatted).toContain('Failed installations: 1') expect(formatted).toContain('Total time: 5000ms') expect(formatted).toContain('bumpx installed: true') expect(formatted).toContain('Bun installed: true') expect(formatted).toContain('pkgx installed: false') }) }) describe('Integration scenarios', () => { it('should validate complete workflow logic', () => { // Simulate the complete action workflow const simulateActionWorkflow = (inputs: any) => { // Input validation if (inputs.timeout <= 0 || inputs.timeout > 3600) { throw new ActionError( `Invalid timeout: ${inputs.timeout}`, ActionErrorType.CONFIG_PARSING_FAILED, ) } // Environment setup const envVars = inputs.envVars ? JSON.parse(inputs.envVars) : {} // Package processing const packages = inputs.packages ? inputs.packages.split(/\s+/).filter(Boolean) : [] // Installation simulation const results = packages.map((pkg: string) => ({ name: pkg, success: pkg !== 'bad-package', // Simulate failure for 'bad-package' installTime: Math.random() * 1000, ...(pkg === 'bad-package' ? { error: 'Package not found' } : { version: '1.0.0' }), })) const summary = { totalPackages: packages.length, successfulInstalls: results.filter((r: any) => r.success).length, failedInstalls: results.filter((r: any) => !r.success).length, results, totalTime: 5000, bumpxInstalled: true, bunInstalled: inputs.installBun, pkgxInstalled: inputs.installPkgx, } return { summary, envVars, packages } } // Test successful workflow const successInputs = { packages: 'typescript eslint', envVars: '{"NODE_ENV":"test"}', timeout: 600, installBun: true, installPkgx: true, } const successResult = simulateActionWorkflow(successInputs) expect(successResult.summary.totalPackages).toBe(2) expect(successResult.summary.successfulInstalls).toBe(2) expect(successResult.summary.failedInstalls).toBe(0) expect(successResult.envVars.NODE_ENV).toBe('test') // Test with failure const failureInputs = { packages: 'typescript bad-package eslint', envVars: '{}', timeout: 300, installBun: false, installPkgx: false, } const failureResult = simulateActionWorkflow(failureInputs) expect(failureResult.summary.totalPackages).toBe(3) expect(failureResult.summary.successfulInstalls).toBe(2) expect(failureResult.summary.failedInstalls).toBe(1) expect(failureResult.summary.bunInstalled).toBe(false) expect(failureResult.summary.pkgxInstalled).toBe(false) // Test invalid timeout const invalidInputs = { packages: '', envVars: '{}', timeout: 0, installBun: true, installPkgx: true, } expect(() => simulateActionWorkflow(invalidInputs)).toThrow('Invalid timeout: 0') }) it('should handle edge cases correctly', () => { // Test empty package list const emptyPackages = '' const packageList = emptyPackages.split(/\s+/).filter(Boolean) expect(packageList).toEqual([]) // Test whitespace-only package list const whitespacePackages = ' \t \n ' const whitespaceList = whitespacePackages.split(/\s+/).filter(Boolean) expect(whitespaceList).toEqual([]) // Test mixed valid and empty package names const mixedPackages = 'typescript eslint prettier' const mixedList = mixedPackages.split(/\s+/).filter(Boolean) expect(mixedList).toEqual(['typescript', 'eslint', 'prettier']) // Test environment variable edge cases expect(() => JSON.parse('{}')).not.toThrow() expect(() => JSON.parse('')).toThrow() expect(() => JSON.parse('null')).not.toThrow() const emptyEnv = JSON.parse('{}') expect(Object.keys(emptyEnv)).toEqual([]) }) }) })