git-auth-helper.test.ts 27 KB

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