import * as core from '@actions/core'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; import * as cache from '@actions/cache'; import fs from 'fs'; import path from 'path'; import osm from 'os'; import each from 'jest-each'; import * as main from '../src/main'; import * as util from '../src/util'; import OfficialBuilds from '../src/distributions/official_builds/official_builds'; describe('main tests', () => { let inputs = {} as any; let os = {} as any; let infoSpy: jest.SpyInstance; let warningSpy: jest.SpyInstance; let inSpy: jest.SpyInstance; let setOutputSpy: jest.SpyInstance; let startGroupSpy: jest.SpyInstance; let endGroupSpy: jest.SpyInstance; let existsSpy: jest.SpyInstance; let getExecOutputSpy: jest.SpyInstance; let parseNodeVersionSpy: jest.SpyInstance; let cnSpy: jest.SpyInstance; let findSpy: jest.SpyInstance; let isCacheActionAvailable: jest.SpyInstance; let setupNodeJsSpy: jest.SpyInstance; beforeEach(() => { inputs = {}; // node os = {}; console.log('::stop-commands::stoptoken'); process.env['GITHUB_PATH'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out process.env['GITHUB_OUTPUT'] = ''; // Stub out ENV file functionality so we can verify it writes to standard out infoSpy = jest.spyOn(core, 'info'); infoSpy.mockImplementation(() => {}); setOutputSpy = jest.spyOn(core, 'setOutput'); setOutputSpy.mockImplementation(() => {}); warningSpy = jest.spyOn(core, 'warning'); warningSpy.mockImplementation(() => {}); startGroupSpy = jest.spyOn(core, 'startGroup'); startGroupSpy.mockImplementation(() => {}); endGroupSpy = jest.spyOn(core, 'endGroup'); endGroupSpy.mockImplementation(() => {}); inSpy = jest.spyOn(core, 'getInput'); inSpy.mockImplementation(name => inputs[name]); getExecOutputSpy = jest.spyOn(exec, 'getExecOutput'); findSpy = jest.spyOn(tc, 'find'); isCacheActionAvailable = jest.spyOn(cache, 'isFeatureAvailable'); existsSpy = jest.spyOn(fs, 'existsSync'); cnSpy = jest.spyOn(process.stdout, 'write'); cnSpy.mockImplementation(line => { // uncomment to debug // process.stderr.write('write:' + line + '\n'); }); setupNodeJsSpy = jest.spyOn(OfficialBuilds.prototype, 'setupNodeJs'); setupNodeJsSpy.mockImplementation(() => {}); }); afterEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); //jest.restoreAllMocks(); }); afterAll(async () => { console.log('::stoptoken::'); jest.restoreAllMocks(); }, 100000); describe('parseNodeVersionFile', () => { each` contents | expected ${'12'} | ${'12'} ${'12.3'} | ${'12.3'} ${'12.3.4'} | ${'12.3.4'} ${'v12.3.4'} | ${'12.3.4'} ${'lts/erbium'} | ${'lts/erbium'} ${'lts/*'} | ${'lts/*'} ${'nodejs 12.3.4'} | ${'12.3.4'} ${'ruby 2.3.4\nnodejs 12.3.4\npython 3.4.5'} | ${'12.3.4'} ${''} | ${''} ${'unknown format'} | ${'unknown format'} ${' 14.1.0 '} | ${'14.1.0'} ${'{"volta": {"node": ">=14.0.0 <=17.0.0"}}'}| ${'>=14.0.0 <=17.0.0'} ${'{"engines": {"node": "17.0.0"}}'} | ${'17.0.0'} ${'{}'} | ${null} `.it('parses "$contents"', ({contents, expected}) => { expect(util.parseNodeVersionFile(contents)).toBe(expected); }); }); describe('printEnvDetailsAndSetOutput', () => { it.each([ [{node: '12.0.2', npm: '6.3.3', yarn: '1.22.11'}], [{node: '16.0.2', npm: '7.3.3', yarn: '2.22.11'}], [{node: '14.0.1', npm: '8.1.0', yarn: '3.2.1'}], [{node: '17.0.2', npm: '6.3.3', yarn: ''}] ])('Tools versions %p', async obj => { getExecOutputSpy.mockImplementation(async command => { if (Reflect.has(obj, command) && !obj[command]) { return { stdout: '', stderr: `${command} does not exist`, exitCode: 1 }; } return {stdout: obj[command], stderr: '', exitCode: 0}; }); await util.printEnvDetailsAndSetOutput(); expect(setOutputSpy).toHaveBeenCalledWith('node-version', obj['node']); Object.getOwnPropertyNames(obj).forEach(name => { if (!obj[name]) { expect(infoSpy).toHaveBeenCalledWith( `[warning]${name} does not exist` ); } expect(infoSpy).toHaveBeenCalledWith(`${name}: ${obj[name]}`); }); }); }); describe('node-version-file flag', () => { beforeEach(() => { parseNodeVersionSpy = jest.spyOn(util, 'parseNodeVersionFile'); }); it('not used if node-version is provided', async () => { // Arrange inputs['node-version'] = '12'; // Act await main.run(); // Assert expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); }, 10000); it('not used if node-version-file not provided', async () => { // Act await main.run(); // Assert expect(parseNodeVersionSpy).toHaveBeenCalledTimes(0); }); it('reads node-version-file if provided', async () => { // Arrange const versionSpec = 'v14'; const versionFile = '.nvmrc'; const expectedVersionSpec = '14'; process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); inputs['node-version-file'] = versionFile; parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); existsSpy.mockImplementationOnce( input => input === path.join(__dirname, 'data', versionFile) ); // Act await main.run(); // Assert expect(existsSpy).toHaveBeenCalledTimes(1); expect(existsSpy).toHaveReturnedWith(true); expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec); expect(infoSpy).toHaveBeenCalledWith( `Resolved ${versionFile} as ${expectedVersionSpec}` ); }, 10000); it('reads package.json as node-version-file if provided', async () => { // Arrange const versionSpec = fs.readFileSync( path.join(__dirname, 'data/package.json'), 'utf-8' ); const versionFile = 'package.json'; const expectedVersionSpec = '14'; process.env['GITHUB_WORKSPACE'] = path.join(__dirname, 'data'); inputs['node-version-file'] = versionFile; parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); existsSpy.mockImplementationOnce( input => input === path.join(__dirname, 'data', versionFile) ); // Act await main.run(); // Assert expect(existsSpy).toHaveBeenCalledTimes(1); expect(existsSpy).toHaveReturnedWith(true); expect(parseNodeVersionSpy).toHaveBeenCalledWith(versionSpec); expect(infoSpy).toHaveBeenCalledWith( `Resolved ${versionFile} as ${expectedVersionSpec}` ); }, 10000); it('both node-version-file and node-version are provided', async () => { inputs['node-version'] = '12'; const versionSpec = 'v14'; const versionFile = '.nvmrc'; const expectedVersionSpec = '14'; process.env['GITHUB_WORKSPACE'] = path.join(__dirname, '..'); inputs['node-version-file'] = versionFile; parseNodeVersionSpy.mockImplementation(() => expectedVersionSpec); // Act await main.run(); // Assert expect(existsSpy).toHaveBeenCalledTimes(0); expect(parseNodeVersionSpy).not.toHaveBeenCalled(); expect(warningSpy).toHaveBeenCalledWith( 'Both node-version and node-version-file inputs are specified, only node-version will be used' ); }); it('should throw an error if node-version-file is not found', async () => { const versionFile = '.nvmrc'; const versionFilePath = path.join(__dirname, '..', versionFile); inputs['node-version-file'] = versionFile; inSpy.mockImplementation(name => inputs[name]); existsSpy.mockImplementationOnce( input => input === path.join(__dirname, 'data', versionFile) ); // Act await main.run(); // Assert expect(existsSpy).toHaveBeenCalled(); expect(existsSpy).toHaveReturnedWith(false); expect(parseNodeVersionSpy).not.toHaveBeenCalled(); expect(cnSpy).toHaveBeenCalledWith( `::error::The specified node version file at: ${versionFilePath} does not exist${osm.EOL}` ); }); }); describe('cache on GHES', () => { it('Should throw an error, because cache is not supported', async () => { inputs['node-version'] = '12'; inputs['cache'] = 'npm'; inSpy.mockImplementation(name => inputs[name]); const toolPath = path.normalize('/cache/node/12.16.1/x64'); findSpy.mockImplementation(() => toolPath); // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); process.env['GITHUB_SERVER_URL'] = 'https://www.test.com'; isCacheActionAvailable.mockImplementation(() => false); await main.run(); expect(warningSpy).toHaveBeenCalledWith( `Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.` ); }); it('Should throw an internal error', async () => { inputs['node-version'] = '12'; inputs['cache'] = 'npm'; inSpy.mockImplementation(name => inputs[name]); const toolPath = path.normalize('/cache/node/12.16.1/x64'); findSpy.mockImplementation(() => toolPath); // expect(logSpy).toHaveBeenCalledWith(`Found in cache @ ${toolPath}`); process.env['GITHUB_SERVER_URL'] = ''; isCacheActionAvailable.mockImplementation(() => false); await main.run(); expect(warningSpy).toHaveBeenCalledWith( 'The runner was not able to contact the cache service. Caching will be skipped' ); }); }); describe('corepack flag', () => { it('should not enable corepack when no input', async () => { inputs['corepack'] = ''; await main.run(); expect(getExecOutputSpy).not.toHaveBeenCalledWith( 'corepack', expect.anything(), expect.anything() ); }); it('should not enable corepack when input is "false"', async () => { inputs['corepack'] = 'false'; await main.run(); expect(getExecOutputSpy).not.toHaveBeenCalledWith( 'corepack', expect.anything(), expect.anything() ); }); it('should enable corepack when input is "true"', async () => { inputs['corepack'] = 'true'; await main.run(); expect(getExecOutputSpy).toHaveBeenCalledWith( 'corepack', ['enable'], expect.anything() ); }); it('should enable corepack with a single package manager', async () => { inputs['corepack'] = 'npm'; await main.run(); expect(getExecOutputSpy).toHaveBeenCalledWith( 'corepack', ['enable', 'npm'], expect.anything() ); }); it('should enable corepack with multiple package managers', async () => { inputs['corepack'] = 'npm yarn'; await main.run(); expect(getExecOutputSpy).toHaveBeenCalledWith( 'corepack', ['enable', 'npm', 'yarn'], expect.anything() ); }); }); });