/ __test__ / git-auth-helper.test.ts
git-auth-helper.test.ts
  1  import * as core from '@actions/core'
  2  import * as fs from 'fs'
  3  import * as gitAuthHelper from '../lib/git-auth-helper'
  4  import * as io from '@actions/io'
  5  import * as os from 'os'
  6  import * as path from 'path'
  7  import * as stateHelper from '../lib/state-helper'
  8  import {IGitCommandManager} from '../lib/git-command-manager'
  9  import {IGitSourceSettings} from '../lib/git-source-settings'
 10  
 11  const isWindows = process.platform === 'win32'
 12  const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
 13  const originalRunnerTemp = process.env['RUNNER_TEMP']
 14  const originalHome = process.env['HOME']
 15  let workspace: string
 16  let localGitConfigPath: string
 17  let globalGitConfigPath: string
 18  let runnerTemp: string
 19  let tempHomedir: string
 20  let git: IGitCommandManager & {env: {[key: string]: string}}
 21  let settings: IGitSourceSettings
 22  let sshPath: string
 23  
 24  describe('git-auth-helper tests', () => {
 25    beforeAll(async () => {
 26      // SSH
 27      sshPath = await io.which('ssh')
 28  
 29      // Clear test workspace
 30      await io.rmRF(testWorkspace)
 31    })
 32  
 33    beforeEach(() => {
 34      // Mock setSecret
 35      jest.spyOn(core, 'setSecret').mockImplementation((secret: string) => {})
 36  
 37      // Mock error/warning/info/debug
 38      jest.spyOn(core, 'error').mockImplementation(jest.fn())
 39      jest.spyOn(core, 'warning').mockImplementation(jest.fn())
 40      jest.spyOn(core, 'info').mockImplementation(jest.fn())
 41      jest.spyOn(core, 'debug').mockImplementation(jest.fn())
 42  
 43      // Mock state helper
 44      jest.spyOn(stateHelper, 'setSshKeyPath').mockImplementation(jest.fn())
 45      jest
 46        .spyOn(stateHelper, 'setSshKnownHostsPath')
 47        .mockImplementation(jest.fn())
 48    })
 49  
 50    afterEach(() => {
 51      // Unregister mocks
 52      jest.restoreAllMocks()
 53  
 54      // Restore HOME
 55      if (originalHome) {
 56        process.env['HOME'] = originalHome
 57      } else {
 58        delete process.env['HOME']
 59      }
 60    })
 61  
 62    afterAll(() => {
 63      // Restore RUNNER_TEMP
 64      delete process.env['RUNNER_TEMP']
 65      if (originalRunnerTemp) {
 66        process.env['RUNNER_TEMP'] = originalRunnerTemp
 67      }
 68    })
 69  
 70    const configureAuth_configuresAuthHeader =
 71      'configureAuth configures auth header'
 72    it(configureAuth_configuresAuthHeader, async () => {
 73      // Arrange
 74      await setup(configureAuth_configuresAuthHeader)
 75      expect(settings.authToken).toBeTruthy() // sanity check
 76      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
 77  
 78      // Act
 79      await authHelper.configureAuth()
 80  
 81      // Assert config
 82      const configContent = (
 83        await fs.promises.readFile(localGitConfigPath)
 84      ).toString()
 85      const basicCredential = Buffer.from(
 86        `x-access-token:${settings.authToken}`,
 87        'utf8'
 88      ).toString('base64')
 89      expect(
 90        configContent.indexOf(
 91          `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
 92        )
 93      ).toBeGreaterThanOrEqual(0)
 94    })
 95  
 96    const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
 97      'configureAuth configures auth header even when persist credentials false'
 98    it(
 99      configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse,
100      async () => {
101        // Arrange
102        await setup(
103          configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse
104        )
105        expect(settings.authToken).toBeTruthy() // sanity check
106        settings.persistCredentials = false
107        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
108  
109        // Act
110        await authHelper.configureAuth()
111  
112        // Assert config
113        const configContent = (
114          await fs.promises.readFile(localGitConfigPath)
115        ).toString()
116        expect(
117          configContent.indexOf(
118            `http.https://github.com/.extraheader AUTHORIZATION`
119          )
120        ).toBeGreaterThanOrEqual(0)
121      }
122    )
123  
124    const configureAuth_copiesUserKnownHosts =
125      'configureAuth copies user known hosts'
126    it(configureAuth_copiesUserKnownHosts, async () => {
127      if (!sshPath) {
128        process.stdout.write(
129          `Skipped test "${configureAuth_copiesUserKnownHosts}". Executable 'ssh' not found in the PATH.\n`
130        )
131        return
132      }
133  
134      // Arange
135      await setup(configureAuth_copiesUserKnownHosts)
136      expect(settings.sshKey).toBeTruthy() // sanity check
137  
138      // Mock fs.promises.readFile
139      const realReadFile = fs.promises.readFile
140      jest.spyOn(fs.promises, 'readFile').mockImplementation(
141        async (file: any, options: any): Promise<Buffer> => {
142          const userKnownHostsPath = path.join(
143            os.homedir(),
144            '.ssh',
145            'known_hosts'
146          )
147          if (file === userKnownHostsPath) {
148            return Buffer.from('some-domain.com ssh-rsa ABCDEF')
149          }
150  
151          return await realReadFile(file, options)
152        }
153      )
154  
155      // Act
156      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
157      await authHelper.configureAuth()
158  
159      // Assert known hosts
160      const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
161      const actualSshKnownHostsContent = (
162        await fs.promises.readFile(actualSshKnownHostsPath)
163      ).toString()
164      expect(actualSshKnownHostsContent).toMatch(
165        /some-domain\.com ssh-rsa ABCDEF/
166      )
167      expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
168    })
169  
170    const configureAuth_registersBasicCredentialAsSecret =
171      'configureAuth registers basic credential as secret'
172    it(configureAuth_registersBasicCredentialAsSecret, async () => {
173      // Arrange
174      await setup(configureAuth_registersBasicCredentialAsSecret)
175      expect(settings.authToken).toBeTruthy() // sanity check
176      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
177  
178      // Act
179      await authHelper.configureAuth()
180  
181      // Assert secret
182      const setSecretSpy = core.setSecret as jest.Mock<any, any>
183      expect(setSecretSpy).toHaveBeenCalledTimes(1)
184      const expectedSecret = Buffer.from(
185        `x-access-token:${settings.authToken}`,
186        'utf8'
187      ).toString('base64')
188      expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
189    })
190  
191    const setsSshCommandEnvVarWhenPersistCredentialsFalse =
192      'sets SSH command env var when persist-credentials false'
193    it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
194      if (!sshPath) {
195        process.stdout.write(
196          `Skipped test "${setsSshCommandEnvVarWhenPersistCredentialsFalse}". Executable 'ssh' not found in the PATH.\n`
197        )
198        return
199      }
200  
201      // Arrange
202      await setup(setsSshCommandEnvVarWhenPersistCredentialsFalse)
203      settings.persistCredentials = false
204      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
205  
206      // Act
207      await authHelper.configureAuth()
208  
209      // Assert git env var
210      const actualKeyPath = await getActualSshKeyPath()
211      const actualKnownHostsPath = await getActualSshKnownHostsPath()
212      const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
213        actualKeyPath
214      )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
215        actualKnownHostsPath
216      )}"`
217      expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
218        'GIT_SSH_COMMAND',
219        expectedSshCommand
220      )
221  
222      // Asserty git config
223      const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
224        .toString()
225        .split('\n')
226        .filter(x => x)
227      expect(gitConfigLines).toHaveLength(1)
228      expect(gitConfigLines[0]).toMatch(/^http\./)
229    })
230  
231    const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
232      'sets SSH command when persist-credentials true'
233    it(configureAuth_setsSshCommandWhenPersistCredentialsTrue, async () => {
234      if (!sshPath) {
235        process.stdout.write(
236          `Skipped test "${configureAuth_setsSshCommandWhenPersistCredentialsTrue}". Executable 'ssh' not found in the PATH.\n`
237        )
238        return
239      }
240  
241      // Arrange
242      await setup(configureAuth_setsSshCommandWhenPersistCredentialsTrue)
243      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
244  
245      // Act
246      await authHelper.configureAuth()
247  
248      // Assert git env var
249      const actualKeyPath = await getActualSshKeyPath()
250      const actualKnownHostsPath = await getActualSshKnownHostsPath()
251      const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
252        actualKeyPath
253      )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
254        actualKnownHostsPath
255      )}"`
256      expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
257        'GIT_SSH_COMMAND',
258        expectedSshCommand
259      )
260  
261      // Asserty git config
262      expect(git.config).toHaveBeenCalledWith(
263        'core.sshCommand',
264        expectedSshCommand
265      )
266    })
267  
268    const configureAuth_writesExplicitKnownHosts = 'writes explicit known hosts'
269    it(configureAuth_writesExplicitKnownHosts, async () => {
270      if (!sshPath) {
271        process.stdout.write(
272          `Skipped test "${configureAuth_writesExplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
273        )
274        return
275      }
276  
277      // Arrange
278      await setup(configureAuth_writesExplicitKnownHosts)
279      expect(settings.sshKey).toBeTruthy() // sanity check
280      settings.sshKnownHosts = 'my-custom-host.com ssh-rsa ABC123'
281      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
282  
283      // Act
284      await authHelper.configureAuth()
285  
286      // Assert known hosts
287      const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
288      const actualSshKnownHostsContent = (
289        await fs.promises.readFile(actualSshKnownHostsPath)
290      ).toString()
291      expect(actualSshKnownHostsContent).toMatch(
292        /my-custom-host\.com ssh-rsa ABC123/
293      )
294      expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
295    })
296  
297    const configureAuth_writesSshKeyAndImplicitKnownHosts =
298      'writes SSH key and implicit known hosts'
299    it(configureAuth_writesSshKeyAndImplicitKnownHosts, async () => {
300      if (!sshPath) {
301        process.stdout.write(
302          `Skipped test "${configureAuth_writesSshKeyAndImplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
303        )
304        return
305      }
306  
307      // Arrange
308      await setup(configureAuth_writesSshKeyAndImplicitKnownHosts)
309      expect(settings.sshKey).toBeTruthy() // sanity check
310      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
311  
312      // Act
313      await authHelper.configureAuth()
314  
315      // Assert SSH key
316      const actualSshKeyPath = await getActualSshKeyPath()
317      expect(actualSshKeyPath).toBeTruthy()
318      const actualSshKeyContent = (
319        await fs.promises.readFile(actualSshKeyPath)
320      ).toString()
321      expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
322      if (!isWindows) {
323        // Assert read/write for user, not group or others.
324        // Otherwise SSH client will error.
325        expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
326          0o600
327        )
328      }
329  
330      // Assert known hosts
331      const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
332      const actualSshKnownHostsContent = (
333        await fs.promises.readFile(actualSshKnownHostsPath)
334      ).toString()
335      expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
336    })
337  
338    const configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet =
339      'configureGlobalAuth configures URL insteadOf when SSH key not set'
340    it(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet, async () => {
341      // Arrange
342      await setup(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet)
343      settings.sshKey = ''
344      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
345  
346      // Act
347      await authHelper.configureAuth()
348      await authHelper.configureGlobalAuth()
349  
350      // Assert temporary global config
351      expect(git.env['HOME']).toBeTruthy()
352      const configContent = (
353        await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
354      ).toString()
355      expect(
356        configContent.indexOf(`url.https://github.com/.insteadOf git@github.com`)
357      ).toBeGreaterThanOrEqual(0)
358    })
359  
360    const configureGlobalAuth_copiesGlobalGitConfig =
361      'configureGlobalAuth copies global git config'
362    it(configureGlobalAuth_copiesGlobalGitConfig, async () => {
363      // Arrange
364      await setup(configureGlobalAuth_copiesGlobalGitConfig)
365      await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config')
366      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
367  
368      // Act
369      await authHelper.configureAuth()
370      await authHelper.configureGlobalAuth()
371  
372      // Assert original global config not altered
373      let configContent = (
374        await fs.promises.readFile(globalGitConfigPath)
375      ).toString()
376      expect(configContent).toBe('value-from-global-config')
377  
378      // Assert temporary global config
379      expect(git.env['HOME']).toBeTruthy()
380      const basicCredential = Buffer.from(
381        `x-access-token:${settings.authToken}`,
382        'utf8'
383      ).toString('base64')
384      configContent = (
385        await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
386      ).toString()
387      expect(
388        configContent.indexOf('value-from-global-config')
389      ).toBeGreaterThanOrEqual(0)
390      expect(
391        configContent.indexOf(
392          `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
393        )
394      ).toBeGreaterThanOrEqual(0)
395    })
396  
397    const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist =
398      'configureGlobalAuth creates new git config when global does not exist'
399    it(
400      configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist,
401      async () => {
402        // Arrange
403        await setup(
404          configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist
405        )
406        await io.rmRF(globalGitConfigPath)
407        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
408  
409        // Act
410        await authHelper.configureAuth()
411        await authHelper.configureGlobalAuth()
412  
413        // Assert original global config not recreated
414        try {
415          await fs.promises.stat(globalGitConfigPath)
416          throw new Error(
417            `Did not expect file to exist: '${globalGitConfigPath}'`
418          )
419        } catch (err) {
420          if (err.code !== 'ENOENT') {
421            throw err
422          }
423        }
424  
425        // Assert temporary global config
426        expect(git.env['HOME']).toBeTruthy()
427        const basicCredential = Buffer.from(
428          `x-access-token:${settings.authToken}`,
429          'utf8'
430        ).toString('base64')
431        const configContent = (
432          await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
433        ).toString()
434        expect(
435          configContent.indexOf(
436            `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
437          )
438        ).toBeGreaterThanOrEqual(0)
439      }
440    )
441  
442    const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet =
443      'configureSubmoduleAuth configures submodules when persist credentials false and SSH key not set'
444    it(
445      configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet,
446      async () => {
447        // Arrange
448        await setup(
449          configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet
450        )
451        settings.persistCredentials = false
452        settings.sshKey = ''
453        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
454        await authHelper.configureAuth()
455        const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
456        mockSubmoduleForeach.mockClear() // reset calls
457  
458        // Act
459        await authHelper.configureSubmoduleAuth()
460  
461        // Assert
462        expect(mockSubmoduleForeach).toBeCalledTimes(1)
463        expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
464          /unset-all.*insteadOf/
465        )
466      }
467    )
468  
469    const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet =
470      'configureSubmoduleAuth configures submodules when persist credentials false and SSH key set'
471    it(
472      configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet,
473      async () => {
474        if (!sshPath) {
475          process.stdout.write(
476            `Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
477          )
478          return
479        }
480  
481        // Arrange
482        await setup(
483          configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet
484        )
485        settings.persistCredentials = false
486        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
487        await authHelper.configureAuth()
488        const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
489        mockSubmoduleForeach.mockClear() // reset calls
490  
491        // Act
492        await authHelper.configureSubmoduleAuth()
493  
494        // Assert
495        expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
496        expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
497          /unset-all.*insteadOf/
498        )
499      }
500    )
501  
502    const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet =
503      'configureSubmoduleAuth configures submodules when persist credentials true and SSH key not set'
504    it(
505      configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet,
506      async () => {
507        // Arrange
508        await setup(
509          configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet
510        )
511        settings.sshKey = ''
512        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
513        await authHelper.configureAuth()
514        const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
515        mockSubmoduleForeach.mockClear() // reset calls
516  
517        // Act
518        await authHelper.configureSubmoduleAuth()
519  
520        // Assert
521        expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
522        expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
523          /unset-all.*insteadOf/
524        )
525        expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
526        expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/url.*insteadOf/)
527      }
528    )
529  
530    const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet =
531      'configureSubmoduleAuth configures submodules when persist credentials true and SSH key set'
532    it(
533      configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet,
534      async () => {
535        if (!sshPath) {
536          process.stdout.write(
537            `Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
538          )
539          return
540        }
541  
542        // Arrange
543        await setup(
544          configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet
545        )
546        const authHelper = gitAuthHelper.createAuthHelper(git, settings)
547        await authHelper.configureAuth()
548        const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any, any>
549        mockSubmoduleForeach.mockClear() // reset calls
550  
551        // Act
552        await authHelper.configureSubmoduleAuth()
553  
554        // Assert
555        expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
556        expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
557          /unset-all.*insteadOf/
558        )
559        expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/http.*extraheader/)
560        expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(/core\.sshCommand/)
561      }
562    )
563  
564    const removeAuth_removesSshCommand = 'removeAuth removes SSH command'
565    it(removeAuth_removesSshCommand, async () => {
566      if (!sshPath) {
567        process.stdout.write(
568          `Skipped test "${removeAuth_removesSshCommand}". Executable 'ssh' not found in the PATH.\n`
569        )
570        return
571      }
572  
573      // Arrange
574      await setup(removeAuth_removesSshCommand)
575      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
576      await authHelper.configureAuth()
577      let gitConfigContent = (
578        await fs.promises.readFile(localGitConfigPath)
579      ).toString()
580      expect(gitConfigContent.indexOf('core.sshCommand')).toBeGreaterThanOrEqual(
581        0
582      ) // sanity check
583      const actualKeyPath = await getActualSshKeyPath()
584      expect(actualKeyPath).toBeTruthy()
585      await fs.promises.stat(actualKeyPath)
586      const actualKnownHostsPath = await getActualSshKnownHostsPath()
587      expect(actualKnownHostsPath).toBeTruthy()
588      await fs.promises.stat(actualKnownHostsPath)
589  
590      // Act
591      await authHelper.removeAuth()
592  
593      // Assert git config
594      gitConfigContent = (
595        await fs.promises.readFile(localGitConfigPath)
596      ).toString()
597      expect(gitConfigContent.indexOf('core.sshCommand')).toBeLessThan(0)
598  
599      // Assert SSH key file
600      try {
601        await fs.promises.stat(actualKeyPath)
602        throw new Error('SSH key should have been deleted')
603      } catch (err) {
604        if (err.code !== 'ENOENT') {
605          throw err
606        }
607      }
608  
609      // Assert known hosts file
610      try {
611        await fs.promises.stat(actualKnownHostsPath)
612        throw new Error('SSH known hosts should have been deleted')
613      } catch (err) {
614        if (err.code !== 'ENOENT') {
615          throw err
616        }
617      }
618    })
619  
620    const removeAuth_removesToken = 'removeAuth removes token'
621    it(removeAuth_removesToken, async () => {
622      // Arrange
623      await setup(removeAuth_removesToken)
624      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
625      await authHelper.configureAuth()
626      let gitConfigContent = (
627        await fs.promises.readFile(localGitConfigPath)
628      ).toString()
629      expect(gitConfigContent.indexOf('http.')).toBeGreaterThanOrEqual(0) // sanity check
630  
631      // Act
632      await authHelper.removeAuth()
633  
634      // Assert git config
635      gitConfigContent = (
636        await fs.promises.readFile(localGitConfigPath)
637      ).toString()
638      expect(gitConfigContent.indexOf('http.')).toBeLessThan(0)
639    })
640  
641    const removeGlobalAuth_removesOverride = 'removeGlobalAuth removes override'
642    it(removeGlobalAuth_removesOverride, async () => {
643      // Arrange
644      await setup(removeGlobalAuth_removesOverride)
645      const authHelper = gitAuthHelper.createAuthHelper(git, settings)
646      await authHelper.configureAuth()
647      await authHelper.configureGlobalAuth()
648      const homeOverride = git.env['HOME'] // Sanity check
649      expect(homeOverride).toBeTruthy()
650      await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
651  
652      // Act
653      await authHelper.removeGlobalAuth()
654  
655      // Assert
656      expect(git.env['HOME']).toBeUndefined()
657      try {
658        await fs.promises.stat(homeOverride)
659        throw new Error(`Should have been deleted '${homeOverride}'`)
660      } catch (err) {
661        if (err.code !== 'ENOENT') {
662          throw err
663        }
664      }
665    })
666  })
667  
668  async function setup(testName: string): Promise<void> {
669    testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
670  
671    // Directories
672    workspace = path.join(testWorkspace, testName, 'workspace')
673    runnerTemp = path.join(testWorkspace, testName, 'runner-temp')
674    tempHomedir = path.join(testWorkspace, testName, 'home-dir')
675    await fs.promises.mkdir(workspace, {recursive: true})
676    await fs.promises.mkdir(runnerTemp, {recursive: true})
677    await fs.promises.mkdir(tempHomedir, {recursive: true})
678    process.env['RUNNER_TEMP'] = runnerTemp
679    process.env['HOME'] = tempHomedir
680  
681    // Create git config
682    globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
683    await fs.promises.writeFile(globalGitConfigPath, '')
684    localGitConfigPath = path.join(workspace, '.git', 'config')
685    await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true})
686    await fs.promises.writeFile(localGitConfigPath, '')
687  
688    git = {
689      branchDelete: jest.fn(),
690      branchExists: jest.fn(),
691      branchList: jest.fn(),
692      checkout: jest.fn(),
693      checkoutDetach: jest.fn(),
694      config: jest.fn(
695        async (key: string, value: string, globalConfig?: boolean) => {
696          const configPath = globalConfig
697            ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
698            : localGitConfigPath
699          await fs.promises.appendFile(configPath, `\n${key} ${value}`)
700        }
701      ),
702      configExists: jest.fn(
703        async (key: string, globalConfig?: boolean): Promise<boolean> => {
704          const configPath = globalConfig
705            ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
706            : localGitConfigPath
707          const content = await fs.promises.readFile(configPath)
708          const lines = content
709            .toString()
710            .split('\n')
711            .filter(x => x)
712          return lines.some(x => x.startsWith(key))
713        }
714      ),
715      env: {},
716      fetch: jest.fn(),
717      getDefaultBranch: jest.fn(),
718      getWorkingDirectory: jest.fn(() => workspace),
719      init: jest.fn(),
720      isDetached: jest.fn(),
721      lfsFetch: jest.fn(),
722      lfsInstall: jest.fn(),
723      log1: jest.fn(),
724      remoteAdd: jest.fn(),
725      removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
726      revParse: jest.fn(),
727      setEnvironmentVariable: jest.fn((name: string, value: string) => {
728        git.env[name] = value
729      }),
730      shaExists: jest.fn(),
731      submoduleForeach: jest.fn(async () => {
732        return ''
733      }),
734      submoduleSync: jest.fn(),
735      submoduleUpdate: jest.fn(),
736      tagExists: jest.fn(),
737      tryClean: jest.fn(),
738      tryConfigUnset: jest.fn(
739        async (key: string, globalConfig?: boolean): Promise<boolean> => {
740          const configPath = globalConfig
741            ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
742            : localGitConfigPath
743          let content = await fs.promises.readFile(configPath)
744          let lines = content
745            .toString()
746            .split('\n')
747            .filter(x => x)
748            .filter(x => !x.startsWith(key))
749          await fs.promises.writeFile(configPath, lines.join('\n'))
750          return true
751        }
752      ),
753      tryDisableAutomaticGarbageCollection: jest.fn(),
754      tryGetFetchUrl: jest.fn(),
755      tryReset: jest.fn()
756    }
757  
758    settings = {
759      authToken: 'some auth token',
760      clean: true,
761      commit: '',
762      fetchDepth: 1,
763      lfs: false,
764      submodules: false,
765      nestedSubmodules: false,
766      persistCredentials: true,
767      ref: 'refs/heads/main',
768      repositoryName: 'my-repo',
769      repositoryOwner: 'my-org',
770      repositoryPath: '',
771      sshKey: sshPath ? 'some ssh private key' : '',
772      sshKnownHosts: '',
773      sshStrict: true
774    }
775  }
776  
777  async function getActualSshKeyPath(): Promise<string> {
778    let actualTempFiles = (await fs.promises.readdir(runnerTemp))
779      .sort()
780      .map(x => path.join(runnerTemp, x))
781    if (actualTempFiles.length === 0) {
782      return ''
783    }
784  
785    expect(actualTempFiles).toHaveLength(2)
786    expect(actualTempFiles[0].endsWith('_known_hosts')).toBeFalsy()
787    return actualTempFiles[0]
788  }
789  
790  async function getActualSshKnownHostsPath(): Promise<string> {
791    let actualTempFiles = (await fs.promises.readdir(runnerTemp))
792      .sort()
793      .map(x => path.join(runnerTemp, x))
794    if (actualTempFiles.length === 0) {
795      return ''
796    }
797  
798    expect(actualTempFiles).toHaveLength(2)
799    expect(actualTempFiles[1].endsWith('_known_hosts')).toBeTruthy()
800    expect(actualTempFiles[1].startsWith(actualTempFiles[0])).toBeTruthy()
801    return actualTempFiles[1]
802  }