diff options
Diffstat (limited to '__test__')
| -rw-r--r-- | __test__/git-auth-helper.test.ts | 200 | ||||
| -rw-r--r-- | __test__/git-directory-helper.test.ts | 382 | ||||
| -rw-r--r-- | __test__/input-helper.test.ts | 24 |
3 files changed, 597 insertions, 9 deletions
diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts new file mode 100644 index 0000000..fab9f37 --- /dev/null +++ b/__test__/git-auth-helper.test.ts @@ -0,0 +1,200 @@ +import * as core from '@actions/core' +import * as fs from 'fs' +import * as gitAuthHelper from '../lib/git-auth-helper' +import * as io from '@actions/io' +import * as path from 'path' +import {IGitCommandManager} from '../lib/git-command-manager' +import {IGitSourceSettings} from '../lib/git-source-settings' + +const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper') +const originalRunnerTemp = process.env['RUNNER_TEMP'] +let workspace: string +let gitConfigPath: string +let runnerTemp: string +let git: IGitCommandManager +let settings: IGitSourceSettings + +describe('git-auth-helper tests', () => { + beforeAll(async () => { + // Clear test workspace + await io.rmRF(testWorkspace) + }) + + beforeEach(() => { + // Mock setSecret + jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {}) + }) + + afterEach(() => { + // Unregister mocks + jest.restoreAllMocks() + }) + + afterAll(() => { + // Restore RUNNER_TEMP + delete process.env['RUNNER_TEMP'] + if (originalRunnerTemp) { + process.env['RUNNER_TEMP'] = originalRunnerTemp + } + }) + + const configuresAuthHeader = 'configures auth header' + it(configuresAuthHeader, async () => { + // Arrange + await setup(configuresAuthHeader) + expect(settings.authToken).toBeTruthy() // sanity check + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + + // Act + await authHelper.configureAuth() + + // Assert config + const configContent = (await fs.promises.readFile(gitConfigPath)).toString() + const basicCredential = Buffer.from( + `x-access-token:${settings.authToken}`, + 'utf8' + ).toString('base64') + expect( + configContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` + ) + ).toBeGreaterThanOrEqual(0) + }) + + const configuresAuthHeaderEvenWhenPersistCredentialsFalse = + 'configures auth header even when persist credentials false' + it(configuresAuthHeaderEvenWhenPersistCredentialsFalse, async () => { + // Arrange + await setup(configuresAuthHeaderEvenWhenPersistCredentialsFalse) + expect(settings.authToken).toBeTruthy() // sanity check + settings.persistCredentials = false + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + + // Act + await authHelper.configureAuth() + + // Assert config + const configContent = (await fs.promises.readFile(gitConfigPath)).toString() + expect( + configContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION` + ) + ).toBeGreaterThanOrEqual(0) + }) + + const registersBasicCredentialAsSecret = + 'registers basic credential as secret' + it(registersBasicCredentialAsSecret, async () => { + // Arrange + await setup(registersBasicCredentialAsSecret) + expect(settings.authToken).toBeTruthy() // sanity check + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + + // Act + await authHelper.configureAuth() + + // Assert secret + const setSecretSpy = core.setSecret as jest.Mock<any, any> + expect(setSecretSpy).toHaveBeenCalledTimes(1) + const expectedSecret = Buffer.from( + `x-access-token:${settings.authToken}`, + 'utf8' + ).toString('base64') + expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret) + }) + + const removesToken = 'removes token' + it(removesToken, async () => { + // Arrange + await setup(removesToken) + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + await authHelper.configureAuth() + let gitConfigContent = ( + await fs.promises.readFile(gitConfigPath) + ).toString() + expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check + + // Act + await authHelper.removeAuth() + + // Assert git config + gitConfigContent = (await fs.promises.readFile(gitConfigPath)).toString() + expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) + }) +}) + +async function setup(testName: string): Promise<void> { + testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-') + + // Directories + workspace = path.join(testWorkspace, testName, 'workspace') + runnerTemp = path.join(testWorkspace, testName, 'runner-temp') + await fs.promises.mkdir(workspace, {recursive: true}) + await fs.promises.mkdir(runnerTemp, {recursive: true}) + process.env['RUNNER_TEMP'] = runnerTemp + + // Create git config + gitConfigPath = path.join(workspace, '.git', 'config') + await fs.promises.mkdir(path.join(workspace, '.git'), {recursive: true}) + await fs.promises.writeFile(path.join(workspace, '.git', 'config'), '') + + git = { + branchDelete: jest.fn(), + branchExists: jest.fn(), + branchList: jest.fn(), + checkout: jest.fn(), + checkoutDetach: jest.fn(), + config: jest.fn(async (key: string, value: string) => { + await fs.promises.appendFile(gitConfigPath, `\n${key} ${value}`) + }), + configExists: jest.fn( + async (key: string): Promise<boolean> => { + const content = await fs.promises.readFile(gitConfigPath) + const lines = content + .toString() + .split('\n') + .filter(x => x) + return lines.some(x => x.startsWith(key)) + } + ), + fetch: jest.fn(), + getWorkingDirectory: jest.fn(() => workspace), + init: jest.fn(), + isDetached: jest.fn(), + lfsFetch: jest.fn(), + lfsInstall: jest.fn(), + log1: jest.fn(), + remoteAdd: jest.fn(), + setEnvironmentVariable: jest.fn(), + tagExists: jest.fn(), + tryClean: jest.fn(), + tryConfigUnset: jest.fn( + async (key: string): Promise<boolean> => { + let content = await fs.promises.readFile(gitConfigPath) + let lines = content + .toString() + .split('\n') + .filter(x => x) + .filter(x => !x.startsWith(key)) + await fs.promises.writeFile(gitConfigPath, lines.join('\n')) + return true + } + ), + tryDisableAutomaticGarbageCollection: jest.fn(), + tryGetFetchUrl: jest.fn(), + tryReset: jest.fn() + } + + settings = { + authToken: 'some auth token', + clean: true, + commit: '', + fetchDepth: 1, + lfs: false, + persistCredentials: true, + ref: 'refs/heads/master', + repositoryName: 'my-repo', + repositoryOwner: 'my-org', + repositoryPath: '' + } +} diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts new file mode 100644 index 0000000..4383e1d --- /dev/null +++ b/__test__/git-directory-helper.test.ts @@ -0,0 +1,382 @@ +import * as core from '@actions/core' +import * as fs from 'fs' +import * as gitDirectoryHelper from '../lib/git-directory-helper' +import * as io from '@actions/io' +import * as path from 'path' +import {IGitCommandManager} from '../lib/git-command-manager' + +const testWorkspace = path.join(__dirname, '_temp', 'git-directory-helper') +let repositoryPath: string +let repositoryUrl: string +let clean: boolean +let git: IGitCommandManager + +describe('git-directory-helper tests', () => { + beforeAll(async () => { + // Clear test workspace + await io.rmRF(testWorkspace) + }) + + beforeEach(() => { + // Mock error/warning/info/debug + jest.spyOn(core, 'error').mockImplementation(jest.fn()) + jest.spyOn(core, 'warning').mockImplementation(jest.fn()) + jest.spyOn(core, 'info').mockImplementation(jest.fn()) + jest.spyOn(core, 'debug').mockImplementation(jest.fn()) + }) + + afterEach(() => { + // Unregister mocks + jest.restoreAllMocks() + }) + + const cleansWhenCleanTrue = 'cleans when clean true' + it(cleansWhenCleanTrue, async () => { + // Arrange + await setup(cleansWhenCleanTrue) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.tryClean).toHaveBeenCalled() + expect(git.tryReset).toHaveBeenCalled() + expect(core.warning).not.toHaveBeenCalled() + }) + + const checkoutDetachWhenNotDetached = 'checkout detach when not detached' + it(checkoutDetachWhenNotDetached, async () => { + // Arrange + await setup(checkoutDetachWhenNotDetached) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.checkoutDetach).toHaveBeenCalled() + }) + + const doesNotCheckoutDetachWhenNotAlreadyDetached = + 'does not checkout detach when already detached' + it(doesNotCheckoutDetachWhenNotAlreadyDetached, async () => { + // Arrange + await setup(doesNotCheckoutDetachWhenNotAlreadyDetached) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + const mockIsDetached = git.isDetached as jest.Mock<any, any> + mockIsDetached.mockImplementation(async () => { + return true + }) + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.checkoutDetach).not.toHaveBeenCalled() + }) + + const doesNotCleanWhenCleanFalse = 'does not clean when clean false' + it(doesNotCleanWhenCleanFalse, async () => { + // Arrange + await setup(doesNotCleanWhenCleanFalse) + clean = false + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.isDetached).toHaveBeenCalled() + expect(git.branchList).toHaveBeenCalled() + expect(core.warning).not.toHaveBeenCalled() + expect(git.tryClean).not.toHaveBeenCalled() + expect(git.tryReset).not.toHaveBeenCalled() + }) + + const removesContentsWhenCleanFails = 'removes contents when clean fails' + it(removesContentsWhenCleanFails, async () => { + // Arrange + await setup(removesContentsWhenCleanFails) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + let mockTryClean = git.tryClean as jest.Mock<any, any> + mockTryClean.mockImplementation(async () => { + return false + }) + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files).toHaveLength(0) + expect(git.tryClean).toHaveBeenCalled() + expect(core.warning).toHaveBeenCalled() + expect(git.tryReset).not.toHaveBeenCalled() + }) + + const removesContentsWhenDifferentRepositoryUrl = + 'removes contents when different repository url' + it(removesContentsWhenDifferentRepositoryUrl, async () => { + // Arrange + await setup(removesContentsWhenDifferentRepositoryUrl) + clean = false + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + const differentRepositoryUrl = + 'https://github.com/my-different-org/my-different-repo' + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + differentRepositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files).toHaveLength(0) + expect(core.warning).not.toHaveBeenCalled() + expect(git.isDetached).not.toHaveBeenCalled() + }) + + const removesContentsWhenNoGitDirectory = + 'removes contents when no git directory' + it(removesContentsWhenNoGitDirectory, async () => { + // Arrange + await setup(removesContentsWhenNoGitDirectory) + clean = false + await io.rmRF(path.join(repositoryPath, '.git')) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files).toHaveLength(0) + expect(core.warning).not.toHaveBeenCalled() + expect(git.isDetached).not.toHaveBeenCalled() + }) + + const removesContentsWhenResetFails = 'removes contents when reset fails' + it(removesContentsWhenResetFails, async () => { + // Arrange + await setup(removesContentsWhenResetFails) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + let mockTryReset = git.tryReset as jest.Mock<any, any> + mockTryReset.mockImplementation(async () => { + return false + }) + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files).toHaveLength(0) + expect(git.tryClean).toHaveBeenCalled() + expect(git.tryReset).toHaveBeenCalled() + expect(core.warning).toHaveBeenCalled() + }) + + const removesContentsWhenUndefinedGitCommandManager = + 'removes contents when undefined git command manager' + it(removesContentsWhenUndefinedGitCommandManager, async () => { + // Arrange + await setup(removesContentsWhenUndefinedGitCommandManager) + clean = false + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + undefined, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files).toHaveLength(0) + expect(core.warning).not.toHaveBeenCalled() + }) + + const removesLocalBranches = 'removes local branches' + it(removesLocalBranches, async () => { + // Arrange + await setup(removesLocalBranches) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + const mockBranchList = git.branchList as jest.Mock<any, any> + mockBranchList.mockImplementation(async (remote: boolean) => { + return remote ? [] : ['local-branch-1', 'local-branch-2'] + }) + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-1') + expect(git.branchDelete).toHaveBeenCalledWith(false, 'local-branch-2') + }) + + const removesLockFiles = 'removes lock files' + it(removesLockFiles, async () => { + // Arrange + await setup(removesLockFiles) + clean = false + await fs.promises.writeFile( + path.join(repositoryPath, '.git', 'index.lock'), + '' + ) + await fs.promises.writeFile( + path.join(repositoryPath, '.git', 'shallow.lock'), + '' + ) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + let files = await fs.promises.readdir(path.join(repositoryPath, '.git')) + expect(files).toHaveLength(0) + files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.isDetached).toHaveBeenCalled() + expect(git.branchList).toHaveBeenCalled() + expect(core.warning).not.toHaveBeenCalled() + expect(git.tryClean).not.toHaveBeenCalled() + expect(git.tryReset).not.toHaveBeenCalled() + }) + + const removesRemoteBranches = 'removes local branches' + it(removesRemoteBranches, async () => { + // Arrange + await setup(removesRemoteBranches) + await fs.promises.writeFile(path.join(repositoryPath, 'my-file'), '') + const mockBranchList = git.branchList as jest.Mock<any, any> + mockBranchList.mockImplementation(async (remote: boolean) => { + return remote ? ['remote-branch-1', 'remote-branch-2'] : [] + }) + + // Act + await gitDirectoryHelper.prepareExistingDirectory( + git, + repositoryPath, + repositoryUrl, + clean + ) + + // Assert + const files = await fs.promises.readdir(repositoryPath) + expect(files.sort()).toEqual(['.git', 'my-file']) + expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-1') + expect(git.branchDelete).toHaveBeenCalledWith(true, 'remote-branch-2') + }) +}) + +async function setup(testName: string): Promise<void> { + testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-') + + // Repository directory + repositoryPath = path.join(testWorkspace, testName) + await fs.promises.mkdir(path.join(repositoryPath, '.git'), {recursive: true}) + + // Repository URL + repositoryUrl = 'https://github.com/my-org/my-repo' + + // Clean + clean = true + + // Git command manager + git = { + branchDelete: jest.fn(), + branchExists: jest.fn(), + branchList: jest.fn(async () => { + return [] + }), + checkout: jest.fn(), + checkoutDetach: jest.fn(), + config: jest.fn(), + configExists: jest.fn(), + fetch: jest.fn(), + getWorkingDirectory: jest.fn(() => repositoryPath), + init: jest.fn(), + isDetached: jest.fn(), + lfsFetch: jest.fn(), + lfsInstall: jest.fn(), + log1: jest.fn(), + remoteAdd: jest.fn(), + setEnvironmentVariable: jest.fn(), + tagExists: jest.fn(), + tryClean: jest.fn(async () => { + return true + }), + tryConfigUnset: jest.fn(), + tryDisableAutomaticGarbageCollection: jest.fn(), + tryGetFetchUrl: jest.fn(async () => { + // Sanity check - this function shouldn't be called when the .git directory doesn't exist + await fs.promises.stat(path.join(repositoryPath, '.git')) + return repositoryUrl + }), + tryReset: jest.fn(async () => { + return true + }) + } +} diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts index be4b166..53c7000 100644 --- a/__test__/input-helper.test.ts +++ b/__test__/input-helper.test.ts @@ -4,7 +4,7 @@ import * as fsHelper from '../lib/fs-helper' import * as github from '@actions/github' import * as inputHelper from '../lib/input-helper' import * as path from 'path' -import {ISourceSettings} from '../lib/git-source-provider' +import {IGitSourceSettings} from '../lib/git-source-settings' const originalGitHubWorkspace = process.env['GITHUB_WORKSPACE'] const gitHubWorkspace = path.resolve('/checkout-tests/workspace') @@ -17,12 +17,18 @@ let originalContext = {...github.context} describe('input-helper tests', () => { beforeAll(() => { - // Mock @actions/core getInput() + // Mock getInput jest.spyOn(core, 'getInput').mockImplementation((name: string) => { return inputs[name] }) - // Mock @actions/github context + // Mock error/warning/info/debug + jest.spyOn(core, 'error').mockImplementation(jest.fn()) + jest.spyOn(core, 'warning').mockImplementation(jest.fn()) + jest.spyOn(core, 'info').mockImplementation(jest.fn()) + jest.spyOn(core, 'debug').mockImplementation(jest.fn()) + + // Mock github context jest.spyOn(github.context, 'repo', 'get').mockImplementation(() => { return { owner: 'some-owner', @@ -62,7 +68,7 @@ describe('input-helper tests', () => { }) it('sets defaults', () => { - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings).toBeTruthy() expect(settings.authToken).toBeFalsy() expect(settings.clean).toBe(true) @@ -80,7 +86,7 @@ describe('input-helper tests', () => { let originalRef = github.context.ref try { github.context.ref = 'some-unqualified-ref' - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings).toBeTruthy() expect(settings.commit).toBe('1234567890123456789012345678901234567890') expect(settings.ref).toBe('refs/heads/some-unqualified-ref') @@ -98,7 +104,7 @@ describe('input-helper tests', () => { it('roots path', () => { inputs.path = 'some-directory/some-subdirectory' - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings.repositoryPath).toBe( path.join(gitHubWorkspace, 'some-directory', 'some-subdirectory') ) @@ -106,21 +112,21 @@ describe('input-helper tests', () => { it('sets correct default ref/sha for other repo', () => { inputs.repository = 'some-owner/some-other-repo' - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings.ref).toBe('refs/heads/master') expect(settings.commit).toBeFalsy() }) it('sets ref to empty when explicit sha', () => { inputs.ref = '1111111111222222222233333333334444444444' - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings.ref).toBeFalsy() expect(settings.commit).toBe('1111111111222222222233333333334444444444') }) it('sets sha to empty when explicit ref', () => { inputs.ref = 'refs/heads/some-other-ref' - const settings: ISourceSettings = inputHelper.getInputs() + const settings: IGitSourceSettings = inputHelper.getInputs() expect(settings.ref).toBe('refs/heads/some-other-ref') expect(settings.commit).toBeFalsy() }) |
