From 7ca5766bfacc6c307d7e69645aa94ac44318af46 Mon Sep 17 00:00:00 2001 From: eric sciple Date: Thu, 5 Mar 2020 14:21:59 -0500 Subject: add support for submodules (#173) --- __test__/git-auth-helper.test.ts | 296 ++++++++++++++++++++++---- __test__/git-directory-helper.test.ts | 4 + __test__/input-helper.test.ts | 7 - __test__/verify-submodules-false.sh | 11 + __test__/verify-submodules-not-checked-out.sh | 11 - __test__/verify-submodules-recursive.sh | 26 +++ __test__/verify-submodules-true.sh | 26 +++ 7 files changed, 318 insertions(+), 63 deletions(-) create mode 100755 __test__/verify-submodules-false.sh delete mode 100755 __test__/verify-submodules-not-checked-out.sh create mode 100755 __test__/verify-submodules-recursive.sh create mode 100755 __test__/verify-submodules-true.sh (limited to '__test__') diff --git a/__test__/git-auth-helper.test.ts b/__test__/git-auth-helper.test.ts index fab9f37..68926f2 100644 --- a/__test__/git-auth-helper.test.ts +++ b/__test__/git-auth-helper.test.ts @@ -8,10 +8,13 @@ import {IGitSourceSettings} from '../lib/git-source-settings' const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper') const originalRunnerTemp = process.env['RUNNER_TEMP'] +const originalHome = process.env['HOME'] let workspace: string -let gitConfigPath: string +let localGitConfigPath: string +let globalGitConfigPath: string let runnerTemp: string -let git: IGitCommandManager +let tempHomedir: string +let git: IGitCommandManager & {env: {[key: string]: string}} let settings: IGitSourceSettings describe('git-auth-helper tests', () => { @@ -23,11 +26,24 @@ describe('git-auth-helper tests', () => { beforeEach(() => { // Mock setSecret jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {}) + + // 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() + + // Restore HOME + if (originalHome) { + process.env['HOME'] = originalHome + } else { + delete process.env['HOME'] + } }) afterAll(() => { @@ -38,10 +54,11 @@ describe('git-auth-helper tests', () => { } }) - const configuresAuthHeader = 'configures auth header' - it(configuresAuthHeader, async () => { + const configureAuth_configuresAuthHeader = + 'configureAuth configures auth header' + it(configureAuth_configuresAuthHeader, async () => { // Arrange - await setup(configuresAuthHeader) + await setup(configureAuth_configuresAuthHeader) expect(settings.authToken).toBeTruthy() // sanity check const authHelper = gitAuthHelper.createAuthHelper(git, settings) @@ -49,7 +66,9 @@ describe('git-auth-helper tests', () => { await authHelper.configureAuth() // Assert config - const configContent = (await fs.promises.readFile(gitConfigPath)).toString() + const configContent = ( + await fs.promises.readFile(localGitConfigPath) + ).toString() const basicCredential = Buffer.from( `x-access-token:${settings.authToken}`, 'utf8' @@ -61,32 +80,39 @@ describe('git-auth-helper tests', () => { ).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) + const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse = + 'configureAuth configures auth header even when persist credentials false' + it( + configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse, + async () => { + // Arrange + await setup( + configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse + ) + expect(settings.authToken).toBeTruthy() // sanity check + settings.persistCredentials = false + const authHelper = gitAuthHelper.createAuthHelper(git, settings) - // Act - await authHelper.configureAuth() + // 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) - }) + // Assert config + const configContent = ( + await fs.promises.readFile(localGitConfigPath) + ).toString() + expect( + configContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION` + ) + ).toBeGreaterThanOrEqual(0) + } + ) - const registersBasicCredentialAsSecret = - 'registers basic credential as secret' - it(registersBasicCredentialAsSecret, async () => { + const configureAuth_registersBasicCredentialAsSecret = + 'configureAuth registers basic credential as secret' + it(configureAuth_registersBasicCredentialAsSecret, async () => { // Arrange - await setup(registersBasicCredentialAsSecret) + await setup(configureAuth_registersBasicCredentialAsSecret) expect(settings.authToken).toBeTruthy() // sanity check const authHelper = gitAuthHelper.createAuthHelper(git, settings) @@ -103,14 +129,139 @@ describe('git-auth-helper tests', () => { expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret) }) - const removesToken = 'removes token' - it(removesToken, async () => { + const configureGlobalAuth_copiesGlobalGitConfig = + 'configureGlobalAuth copies global git config' + it(configureGlobalAuth_copiesGlobalGitConfig, async () => { + // Arrange + await setup(configureGlobalAuth_copiesGlobalGitConfig) + await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config') + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + + // Act + await authHelper.configureAuth() + await authHelper.configureGlobalAuth() + + // Assert original global config not altered + let configContent = ( + await fs.promises.readFile(globalGitConfigPath) + ).toString() + expect(configContent).toBe('value-from-global-config') + + // Assert temporary global config + expect(git.env['HOME']).toBeTruthy() + const basicCredential = Buffer.from( + `x-access-token:${settings.authToken}`, + 'utf8' + ).toString('base64') + configContent = ( + await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) + ).toString() + expect( + configContent.indexOf('value-from-global-config') + ).toBeGreaterThanOrEqual(0) + expect( + configContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` + ) + ).toBeGreaterThanOrEqual(0) + }) + + const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist = + 'configureGlobalAuth creates new git config when global does not exist' + it( + configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist, + async () => { + // Arrange + await setup( + configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist + ) + await io.rmRF(globalGitConfigPath) + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + + // Act + await authHelper.configureAuth() + await authHelper.configureGlobalAuth() + + // Assert original global config not recreated + try { + await fs.promises.stat(globalGitConfigPath) + throw new Error( + `Did not expect file to exist: '${globalGitConfigPath}'` + ) + } catch (err) { + if (err.code !== 'ENOENT') { + throw err + } + } + + // Assert temporary global config + expect(git.env['HOME']).toBeTruthy() + const basicCredential = Buffer.from( + `x-access-token:${settings.authToken}`, + 'utf8' + ).toString('base64') + const configContent = ( + await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig')) + ).toString() + expect( + configContent.indexOf( + `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}` + ) + ).toBeGreaterThanOrEqual(0) + } + ) + + const configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse = + 'configureSubmoduleAuth does not configure token when persist credentials false' + it( + configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse, + async () => { + // Arrange + await setup( + configureSubmoduleAuth_doesNotConfigureTokenWhenPersistCredentialsFalse + ) + settings.persistCredentials = false + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + await authHelper.configureAuth() + ;(git.submoduleForeach as jest.Mock).mockClear() // reset calls + + // Act + await authHelper.configureSubmoduleAuth() + + // Assert + expect(git.submoduleForeach).not.toHaveBeenCalled() + } + ) + + const configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue = + 'configureSubmoduleAuth configures token when persist credentials true' + it( + configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue, + async () => { + // Arrange + await setup( + configureSubmoduleAuth_configuresTokenWhenPersistCredentialsTrue + ) + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + await authHelper.configureAuth() + ;(git.submoduleForeach as jest.Mock).mockClear() // reset calls + + // Act + await authHelper.configureSubmoduleAuth() + + // Assert + expect(git.submoduleForeach).toHaveBeenCalledTimes(1) + } + ) + + const removeAuth_removesToken = 'removeAuth removes token' + it(removeAuth_removesToken, async () => { // Arrange - await setup(removesToken) + await setup(removeAuth_removesToken) const authHelper = gitAuthHelper.createAuthHelper(git, settings) await authHelper.configureAuth() let gitConfigContent = ( - await fs.promises.readFile(gitConfigPath) + await fs.promises.readFile(localGitConfigPath) ).toString() expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check @@ -118,9 +269,37 @@ describe('git-auth-helper tests', () => { await authHelper.removeAuth() // Assert git config - gitConfigContent = (await fs.promises.readFile(gitConfigPath)).toString() + gitConfigContent = ( + await fs.promises.readFile(localGitConfigPath) + ).toString() expect(gitConfigContent.indexOf('http.')).toBeLessThan(0) }) + + const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override' + it(removeGlobalAuth_removesOverride, async () => { + // Arrange + await setup(removeGlobalAuth_removesOverride) + const authHelper = gitAuthHelper.createAuthHelper(git, settings) + await authHelper.configureAuth() + await authHelper.configureGlobalAuth() + const homeOverride = git.env['HOME'] // Sanity check + expect(homeOverride).toBeTruthy() + await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig')) + + // Act + await authHelper.removeGlobalAuth() + + // Assert + expect(git.env['HOME']).toBeUndefined() + try { + await fs.promises.stat(homeOverride) + throw new Error(`Should have been deleted '${homeOverride}'`) + } catch (err) { + if (err.code !== 'ENOENT') { + throw err + } + } + }) }) async function setup(testName: string): Promise { @@ -129,14 +308,19 @@ async function setup(testName: string): Promise { // Directories workspace = path.join(testWorkspace, testName, 'workspace') runnerTemp = path.join(testWorkspace, testName, 'runner-temp') + tempHomedir = path.join(testWorkspace, testName, 'home-dir') await fs.promises.mkdir(workspace, {recursive: true}) await fs.promises.mkdir(runnerTemp, {recursive: true}) + await fs.promises.mkdir(tempHomedir, {recursive: true}) process.env['RUNNER_TEMP'] = runnerTemp + process.env['HOME'] = tempHomedir // 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'), '') + globalGitConfigPath = path.join(tempHomedir, '.gitconfig') + await fs.promises.writeFile(globalGitConfigPath, '') + localGitConfigPath = path.join(workspace, '.git', 'config') + await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true}) + await fs.promises.writeFile(localGitConfigPath, '') git = { branchDelete: jest.fn(), @@ -144,12 +328,20 @@ async function setup(testName: string): Promise { 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}`) - }), + config: jest.fn( + async (key: string, value: string, globalConfig?: boolean) => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + await fs.promises.appendFile(configPath, `\n${key} ${value}`) + } + ), configExists: jest.fn( - async (key: string): Promise => { - const content = await fs.promises.readFile(gitConfigPath) + async (key: string, globalConfig?: boolean): Promise => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + const content = await fs.promises.readFile(configPath) const lines = content .toString() .split('\n') @@ -157,6 +349,7 @@ async function setup(testName: string): Promise { return lines.some(x => x.startsWith(key)) } ), + env: {}, fetch: jest.fn(), getWorkingDirectory: jest.fn(() => workspace), init: jest.fn(), @@ -165,18 +358,29 @@ async function setup(testName: string): Promise { lfsInstall: jest.fn(), log1: jest.fn(), remoteAdd: jest.fn(), - setEnvironmentVariable: jest.fn(), + removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]), + setEnvironmentVariable: jest.fn((name: string, value: string) => { + git.env[name] = value + }), + submoduleForeach: jest.fn(async () => { + return '' + }), + submoduleSync: jest.fn(), + submoduleUpdate: jest.fn(), tagExists: jest.fn(), tryClean: jest.fn(), tryConfigUnset: jest.fn( - async (key: string): Promise => { - let content = await fs.promises.readFile(gitConfigPath) + async (key: string, globalConfig?: boolean): Promise => { + const configPath = globalConfig + ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig') + : localGitConfigPath + let content = await fs.promises.readFile(configPath) let lines = content .toString() .split('\n') .filter(x => x) .filter(x => !x.startsWith(key)) - await fs.promises.writeFile(gitConfigPath, lines.join('\n')) + await fs.promises.writeFile(configPath, lines.join('\n')) return true } ), @@ -191,6 +395,8 @@ async function setup(testName: string): Promise { commit: '', fetchDepth: 1, lfs: false, + submodules: false, + nestedSubmodules: false, persistCredentials: true, ref: 'refs/heads/master', repositoryName: 'my-repo', diff --git a/__test__/git-directory-helper.test.ts b/__test__/git-directory-helper.test.ts index 4383e1d..c39a2a5 100644 --- a/__test__/git-directory-helper.test.ts +++ b/__test__/git-directory-helper.test.ts @@ -363,7 +363,11 @@ async function setup(testName: string): Promise { lfsInstall: jest.fn(), log1: jest.fn(), remoteAdd: jest.fn(), + removeEnvironmentVariable: jest.fn(), setEnvironmentVariable: jest.fn(), + submoduleForeach: jest.fn(), + submoduleSync: jest.fn(), + submoduleUpdate: jest.fn(), tagExists: jest.fn(), tryClean: jest.fn(async () => { return true diff --git a/__test__/input-helper.test.ts b/__test__/input-helper.test.ts index 53c7000..00732ef 100644 --- a/__test__/input-helper.test.ts +++ b/__test__/input-helper.test.ts @@ -130,11 +130,4 @@ describe('input-helper tests', () => { expect(settings.ref).toBe('refs/heads/some-other-ref') expect(settings.commit).toBeFalsy() }) - - it('gives good error message for submodules input', () => { - inputs.submodules = 'true' - assert.throws(() => { - inputHelper.getInputs() - }, /The input 'submodules' is not supported/) - }) }) diff --git a/__test__/verify-submodules-false.sh b/__test__/verify-submodules-false.sh new file mode 100755 index 0000000..733e247 --- /dev/null +++ b/__test__/verify-submodules-false.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ ! -f "./submodules-false/regular-file.txt" ]; then + echo "Expected regular file does not exist" + exit 1 +fi + +if [ -f "./submodules-false/submodule-level-1/submodule-file.txt" ]; then + echo "Unexpected submodule file exists" + exit 1 +fi \ No newline at end of file diff --git a/__test__/verify-submodules-not-checked-out.sh b/__test__/verify-submodules-not-checked-out.sh deleted file mode 100755 index dcc0487..0000000 --- a/__test__/verify-submodules-not-checked-out.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -if [ ! -f "./submodules-not-checked-out/regular-file.txt" ]; then - echo "Expected regular file does not exist" - exit 1 -fi - -if [ -f "./submodules-not-checked-out/submodule-level-1/submodule-file.txt" ]; then - echo "Unexpected submodule file exists" - exit 1 -fi diff --git a/__test__/verify-submodules-recursive.sh b/__test__/verify-submodules-recursive.sh new file mode 100755 index 0000000..1b68f9b --- /dev/null +++ b/__test__/verify-submodules-recursive.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +if [ ! -f "./submodules-recursive/regular-file.txt" ]; then + echo "Expected regular file does not exist" + exit 1 +fi + +if [ ! -f "./submodules-recursive/submodule-level-1/submodule-file.txt" ]; then + echo "Expected submodule file does not exist" + exit 1 +fi + +if [ ! -f "./submodules-recursive/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then + echo "Expected nested submodule file does not exists" + exit 1 +fi + +echo "Testing persisted credential" +pushd ./submodules-recursive/submodule-level-1/submodule-level-2 +git config --local --name-only --get-regexp http.+extraheader && git fetch +if [ "$?" != "0" ]; then + echo "Failed to validate persisted credential" + popd + exit 1 +fi +popd diff --git a/__test__/verify-submodules-true.sh b/__test__/verify-submodules-true.sh new file mode 100755 index 0000000..43769fe --- /dev/null +++ b/__test__/verify-submodules-true.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +if [ ! -f "./submodules-true/regular-file.txt" ]; then + echo "Expected regular file does not exist" + exit 1 +fi + +if [ ! -f "./submodules-true/submodule-level-1/submodule-file.txt" ]; then + echo "Expected submodule file does not exist" + exit 1 +fi + +if [ -f "./submodules-true/submodule-level-1/submodule-level-2/nested-submodule-file.txt" ]; then + echo "Unexpected nested submodule file exists" + exit 1 +fi + +echo "Testing persisted credential" +pushd ./submodules-true/submodule-level-1 +git config --local --name-only --get-regexp http.+extraheader && git fetch +if [ "$?" != "0" ]; then + echo "Failed to validate persisted credential" + popd + exit 1 +fi +popd -- cgit v1.2.3