import { afterEach, beforeEach, describe, expect, it, jest, test } from '@jest/globals'; import semver from 'semver'; import os from 'os'; jest.unstable_mockModule('@actions/core', () => ({ getInput: jest.fn(), getMultilineInput: jest.fn(), getBooleanInput: jest.fn(), setFailed: jest.fn(), warning: jest.fn(), debug: jest.fn(), info: jest.fn(), setOutput: jest.fn(), addPath: jest.fn(), exportVariable: jest.fn() })); jest.unstable_mockModule('fs', () => { const actual = jest.requireActual('fs') as typeof import('fs'); const existsSync = jest.fn(actual.existsSync); const readFileSync = jest.fn(actual.readFileSync); return { ...actual, existsSync, readFileSync, default: {...actual, existsSync, readFileSync} }; }); jest.unstable_mockModule('../src/authutil', () => ({ configAuthentication: jest.fn() })); jest.unstable_mockModule('../src/cache-utils', () => ({ isCacheFeatureAvailable: jest.fn(), getNuGetFolderPath: jest.fn() })); jest.unstable_mockModule('../src/cache-restore', () => ({ restoreCache: jest.fn() })); const core = await import('@actions/core'); const fs = await import('fs'); const auth = await import('../src/authutil.js'); const cacheUtils = await import('../src/cache-utils.js'); const cacheRestore = await import('../src/cache-restore.js'); const setup = await import('../src/setup-dotnet.js'); const {DotnetCoreInstaller, DotnetInstallDir} = await import('../src/installer.js'); describe('setup-dotnet tests', () => { const inputs = {} as any; const getInputSpy = core.getInput as jest.Mock; const getMultilineInputSpy = core.getMultilineInput as jest.Mock; const getBooleanInputSpy = core.getBooleanInput as jest.Mock; const setFailedSpy = core.setFailed as jest.Mock; const warningSpy = core.warning as jest.Mock; const debugSpy = core.debug as jest.Mock; const infoSpy = core.info as jest.Mock; const setOutputSpy = core.setOutput as jest.Mock; const existsSyncSpy = fs.existsSync as jest.Mock; const maxSatisfyingSpy = jest.spyOn(semver, 'maxSatisfying'); const installDotnetSpy = jest.spyOn( DotnetCoreInstaller.prototype, 'installDotnet' ); const isCacheFeatureAvailableSpy = cacheUtils.isCacheFeatureAvailable as jest.Mock; const restoreCacheSpy = cacheRestore.restoreCache as jest.Mock; const configAuthenticationSpy = auth.configAuthentication as jest.Mock; const addToPathOriginal = DotnetInstallDir.addToPath; describe('run() tests', () => { beforeEach(() => { DotnetInstallDir.addToPath = jest.fn(); getMultilineInputSpy.mockImplementation(input => inputs[input as string]); getInputSpy.mockImplementation(input => inputs[input as string]); getBooleanInputSpy.mockImplementation(input => inputs[input as string]); }); afterEach(() => { DotnetInstallDir.addToPath = addToPathOriginal; jest.clearAllMocks(); jest.resetAllMocks(); }); it('should fail the action if global-json-file input is present, but the file does not exist in the file system', async () => { inputs['global-json-file'] = 'fictitious.json'; inputs['dotnet-version'] = []; const expectedErrorMessage = `The specified global.json file '${inputs['global-json-file']}' does not exist`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); test(`if 'dotnet-version' and 'global-json-file' inputs aren't present, should log into debug output, try to find global.json in the repo root, fail and log message into info output`, async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = []; maxSatisfyingSpy.mockImplementation(() => null); setOutputSpy.mockImplementation(() => {}); const expectedDebugMessage = 'No version found, trying to find version from global.json'; const expectedInfoMessage = `The global.json wasn't found in the root directory. No .NET version will be installed.`; await setup.run(); expect(debugSpy).toHaveBeenCalledWith(expectedDebugMessage); expect(existsSyncSpy).toHaveBeenCalled(); expect(infoSpy).toHaveBeenCalledWith(expectedInfoMessage); }); it('should fail the action if quality is supplied but its value is not supported', async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = ['10.0']; inputs['dotnet-quality'] = 'fictitiousQuality'; const expectedErrorMessage = `Value '${inputs['dotnet-quality']}' is not supported for the 'dotnet-quality' option. Supported values are: daily, preview, ga.`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); it('should call installDotnet() multiple times if dotnet-version multiline input is provided', async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = ['9.0', '10.0']; inputs['dotnet-quality'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(installDotnetSpy).toHaveBeenCalledTimes(2); }); it('should call addToPath() after installation complete', async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = ['9.0', '10.0']; inputs['dotnet-quality'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1); }); it('should call auth.configAuthentication() if source-url input is provided', async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = []; inputs['dotnet-quality'] = ''; inputs['source-url'] = 'fictitious.source.url'; configAuthenticationSpy.mockImplementation(() => {}); await setup.run(); expect(configAuthenticationSpy).toHaveBeenCalledWith( inputs['source-url'], undefined ); }); it('should call auth.configAuthentication() with proper parameters if source-url and config-file inputs are provided', async () => { inputs['global-json-file'] = ''; inputs['dotnet-version'] = []; inputs['dotnet-quality'] = ''; inputs['source-url'] = 'fictitious.source.url'; inputs['config-file'] = 'fictitious.path'; configAuthenticationSpy.mockImplementation(() => {}); setOutputSpy.mockImplementation(() => {}); await setup.run(); expect(configAuthenticationSpy).toHaveBeenCalledWith( inputs['source-url'], inputs['config-file'] ); }); it('should call setOutput() after installation complete successfully', async () => { inputs['dotnet-version'] = ['10.0.101']; installDotnetSpy.mockImplementation(() => Promise.resolve(`${inputs['dotnet-version']}`) ); await setup.run(); expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1); }); it(`shouldn't call setOutput() if parsing dotnet-installer logs failed`, async () => { inputs['dotnet-version'] = ['10.0.101']; const warningMessage = `Failed to output the installed version of .NET. The 'dotnet-version' output will not be set.`; installDotnetSpy.mockImplementation(() => Promise.resolve(null)); await setup.run(); expect(warningSpy).toHaveBeenCalledWith(warningMessage); expect(setOutputSpy).not.toHaveBeenCalled(); }); it(`shouldn't call setOutput() if actions didn't install .NET`, async () => { inputs['dotnet-version'] = []; const warningMessage = `The 'dotnet-version' output will not be set.`; await setup.run(); expect(infoSpy).toHaveBeenCalledWith(warningMessage); expect(setOutputSpy).not.toHaveBeenCalled(); }); it(`should get 'cache-dependency-path' and call restoreCache() if input cache is set to true and cache feature is available`, async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['cache'] = true; inputs['cache-dependency-path'] = 'fictitious.package.lock.json'; installDotnetSpy.mockImplementation(() => Promise.resolve('')); isCacheFeatureAvailableSpy.mockImplementation(() => true); restoreCacheSpy.mockImplementation(() => Promise.resolve()); await setup.run(); expect(isCacheFeatureAvailableSpy).toHaveBeenCalledTimes(1); expect(restoreCacheSpy).toHaveBeenCalledWith( inputs['cache-dependency-path'] ); }); it(`shouldn't call restoreCache() if input cache isn't set to true`, async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['cache'] = false; installDotnetSpy.mockImplementation(() => Promise.resolve('')); isCacheFeatureAvailableSpy.mockImplementation(() => true); restoreCacheSpy.mockImplementation(() => Promise.resolve()); await setup.run(); expect(restoreCacheSpy).not.toHaveBeenCalled(); }); it(`shouldn't call restoreCache() if cache feature isn't available`, async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['cache'] = true; installDotnetSpy.mockImplementation(() => Promise.resolve('')); isCacheFeatureAvailableSpy.mockImplementation(() => false); restoreCacheSpy.mockImplementation(() => Promise.resolve()); await setup.run(); expect(restoreCacheSpy).not.toHaveBeenCalled(); }); it('should pass valid architecture input to DotnetCoreInstaller', async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['architecture'] = os.arch().toLowerCase(); installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(installDotnetSpy).toHaveBeenCalledTimes(1); expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1); }); it('should work with empty architecture input for auto-detection', async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(installDotnetSpy).toHaveBeenCalledTimes(1); expect(DotnetInstallDir.addToPath).toHaveBeenCalledTimes(1); }); it('should fail the action if unsupported architecture is provided', async () => { inputs['dotnet-version'] = ['10.0.101']; inputs['dotnet-quality'] = ''; inputs['architecture'] = 'x688'; const expectedErrorMessage = `Value 'x688' is not supported for the 'architecture' option. Supported values are: x64, x86, arm64, amd64, arm, s390x, ppc64le, riscv64.`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); it('should fail the action if unsupported dotnet-channel value is provided with latest', async () => { inputs['dotnet-version'] = ['latest']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = 'invalid'; inputs['architecture'] = ''; const expectedErrorMessage = `Value 'invalid' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); it('should warn but not fail if unsupported dotnet-channel value is provided with a specific version', async () => { inputs['dotnet-version'] = ['8.0.x']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = 'invalid'; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(setFailedSpy).not.toHaveBeenCalled(); expect(warningSpy).toHaveBeenCalledWith( `Value 'invalid' is not supported for the 'dotnet-channel' option and will be ignored because 'dotnet-version' is not set to 'latest'. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).` ); }); it('should pass valid dotnet-channel value through without error', async () => { inputs['dotnet-version'] = ['latest']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = 'LTS'; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(setFailedSpy).not.toHaveBeenCalled(); }); it('should pass A.B channel value through without error when used with latest', async () => { inputs['dotnet-version'] = ['latest']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = '8.0'; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(setFailedSpy).not.toHaveBeenCalled(); }); it('should pass A.B.Cxx channel value through without error when used with latest', async () => { inputs['dotnet-version'] = ['latest']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = '8.0.1xx'; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(setFailedSpy).not.toHaveBeenCalled(); }); it('should fail with A.B.Cxx channel if major version is below 5', async () => { inputs['dotnet-version'] = ['latest']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = '3.1.1xx'; inputs['architecture'] = ''; const expectedErrorMessage = `Value '3.1.1xx' is not supported for the 'dotnet-channel' option. Supported values are: LTS, STS, A.B (e.g. 8.0), A.B.Cxx (e.g. 8.0.1xx).`; await setup.run(); expect(setFailedSpy).toHaveBeenCalledWith(expectedErrorMessage); }); it('should warn and not fail if valid dotnet-channel is provided with a non-latest version', async () => { inputs['dotnet-version'] = ['8.0.x']; inputs['dotnet-quality'] = ''; inputs['dotnet-channel'] = 'LTS'; inputs['architecture'] = ''; installDotnetSpy.mockImplementation(() => Promise.resolve('')); await setup.run(); expect(setFailedSpy).not.toHaveBeenCalled(); expect(warningSpy).toHaveBeenCalledWith( `The 'dotnet-channel' input is only supported when 'dotnet-version' is set to 'latest'.` ); }); }); });