2
0

git-auth-helper.test.ts 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  1. import {
  2. jest,
  3. describe,
  4. it,
  5. expect,
  6. beforeAll,
  7. beforeEach,
  8. afterEach,
  9. afterAll
  10. } from '@jest/globals'
  11. import * as fs from 'fs'
  12. import * as io from '@actions/io'
  13. import * as os from 'os'
  14. import * as path from 'path'
  15. import {fileURLToPath} from 'url'
  16. const __dirname = path.dirname(fileURLToPath(import.meta.url))
  17. // Mock @actions/core before loading git-auth-helper
  18. jest.unstable_mockModule('@actions/core', () => ({
  19. setSecret: jest.fn(),
  20. error: jest.fn(),
  21. warning: jest.fn(),
  22. info: jest.fn(),
  23. debug: jest.fn(),
  24. setFailed: jest.fn()
  25. }))
  26. // Mock state-helper
  27. jest.unstable_mockModule('../src/state-helper.js', () => ({
  28. setSshKeyPath: jest.fn(),
  29. setSshKnownHostsPath: jest.fn(),
  30. IsPost: false,
  31. RepositoryPath: ''
  32. }))
  33. // Dynamic imports after mocking
  34. const core = await import('@actions/core')
  35. const gitAuthHelper = await import('../src/git-auth-helper.js')
  36. type IGitCommandManager =
  37. import('../src/git-command-manager.js').IGitCommandManager
  38. type IGitSourceSettings =
  39. import('../src/git-source-settings.js').IGitSourceSettings
  40. const isWindows = process.platform === 'win32'
  41. const testWorkspace = path.join(__dirname, '_temp', 'git-auth-helper')
  42. const originalRunnerTemp = process.env['RUNNER_TEMP']
  43. const originalHome = process.env['HOME']
  44. let workspace: string
  45. let localGitConfigPath: string
  46. let globalGitConfigPath: string
  47. let runnerTemp: string
  48. let tempHomedir: string
  49. let git: IGitCommandManager & {env: {[key: string]: string}}
  50. let settings: IGitSourceSettings
  51. let sshPath: string
  52. let githubServerUrl: string
  53. describe('git-auth-helper tests', () => {
  54. beforeAll(async () => {
  55. // SSH
  56. sshPath = await io.which('ssh')
  57. // Clear test workspace
  58. await io.rmRF(testWorkspace)
  59. })
  60. beforeEach(() => {
  61. jest.clearAllMocks()
  62. })
  63. afterEach(() => {
  64. // Unregister mocks
  65. jest.clearAllMocks()
  66. // Restore HOME
  67. if (originalHome) {
  68. process.env['HOME'] = originalHome
  69. } else {
  70. delete process.env['HOME']
  71. }
  72. })
  73. afterAll(() => {
  74. // Restore RUNNER_TEMP
  75. delete process.env['RUNNER_TEMP']
  76. if (originalRunnerTemp) {
  77. process.env['RUNNER_TEMP'] = originalRunnerTemp
  78. }
  79. })
  80. async function testAuthHeader(
  81. testName: string,
  82. serverUrl: string | undefined = undefined
  83. ) {
  84. // Arrange
  85. let expectedServerUrl = 'https://github.com'
  86. if (serverUrl) {
  87. githubServerUrl = serverUrl
  88. expectedServerUrl = githubServerUrl
  89. }
  90. await setup(testName)
  91. expect(settings.authToken).toBeTruthy() // sanity check
  92. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  93. // Act
  94. await authHelper.configureAuth()
  95. // Assert config - check that .git/config contains includeIf entries
  96. const localConfigContent = (
  97. await fs.promises.readFile(localGitConfigPath)
  98. ).toString()
  99. expect(
  100. localConfigContent.indexOf('includeIf.gitdir:')
  101. ).toBeGreaterThanOrEqual(0)
  102. // Assert credentials config file contains the actual credentials
  103. const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  104. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  105. )
  106. expect(credentialsFiles.length).toBe(1)
  107. const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
  108. const credentialsContent = (
  109. await fs.promises.readFile(credentialsConfigPath)
  110. ).toString()
  111. const basicCredential = Buffer.from(
  112. `x-access-token:${settings.authToken}`,
  113. 'utf8'
  114. ).toString('base64')
  115. expect(
  116. credentialsContent.indexOf(
  117. `http.${expectedServerUrl}/.extraheader AUTHORIZATION: basic ${basicCredential}`
  118. )
  119. ).toBeGreaterThanOrEqual(0)
  120. }
  121. const configureAuth_configuresAuthHeader =
  122. 'configureAuth configures auth header'
  123. it(configureAuth_configuresAuthHeader, async () => {
  124. await testAuthHeader(configureAuth_configuresAuthHeader)
  125. })
  126. const configureAuth_AcceptsGitHubServerUrl =
  127. 'inject https://my-ghes-server.com as github server url'
  128. it(configureAuth_AcceptsGitHubServerUrl, async () => {
  129. await testAuthHeader(
  130. configureAuth_AcceptsGitHubServerUrl,
  131. 'https://my-ghes-server.com'
  132. )
  133. })
  134. const configureAuth_AcceptsGitHubServerUrlSetToGHEC =
  135. 'inject https://github.com as github server url'
  136. it(configureAuth_AcceptsGitHubServerUrlSetToGHEC, async () => {
  137. await testAuthHeader(
  138. configureAuth_AcceptsGitHubServerUrlSetToGHEC,
  139. 'https://github.com'
  140. )
  141. })
  142. const configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse =
  143. 'configureAuth configures auth header even when persist credentials false'
  144. it(
  145. configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse,
  146. async () => {
  147. // Arrange
  148. await setup(
  149. configureAuth_configuresAuthHeaderEvenWhenPersistCredentialsFalse
  150. )
  151. expect(settings.authToken).toBeTruthy() // sanity check
  152. settings.persistCredentials = false
  153. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  154. // Act
  155. await authHelper.configureAuth()
  156. // Assert config - check credentials config file (not local .git/config)
  157. const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  158. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  159. )
  160. expect(credentialsFiles.length).toBe(1)
  161. const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
  162. const credentialsContent = (
  163. await fs.promises.readFile(credentialsConfigPath)
  164. ).toString()
  165. expect(
  166. credentialsContent.indexOf(
  167. `http.https://github.com/.extraheader AUTHORIZATION`
  168. )
  169. ).toBeGreaterThanOrEqual(0)
  170. }
  171. )
  172. const configureAuth_copiesUserKnownHosts =
  173. 'configureAuth copies user known hosts'
  174. it(configureAuth_copiesUserKnownHosts, async () => {
  175. if (!sshPath) {
  176. process.stdout.write(
  177. `Skipped test "${configureAuth_copiesUserKnownHosts}". Executable 'ssh' not found in the PATH.\n`
  178. )
  179. return
  180. }
  181. // Arange
  182. await setup(configureAuth_copiesUserKnownHosts)
  183. expect(settings.sshKey).toBeTruthy() // sanity check
  184. // Mock fs.promises.readFile
  185. const realReadFile = fs.promises.readFile
  186. jest
  187. .spyOn(fs.promises, 'readFile')
  188. .mockImplementation(async (file: any, options: any): Promise<Buffer> => {
  189. const userKnownHostsPath = path.join(
  190. os.homedir(),
  191. '.ssh',
  192. 'known_hosts'
  193. )
  194. if (file === userKnownHostsPath) {
  195. return Buffer.from('some-domain.com ssh-rsa ABCDEF')
  196. }
  197. return await realReadFile(file, options)
  198. })
  199. // Act
  200. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  201. await authHelper.configureAuth()
  202. // Assert known hosts
  203. const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
  204. const actualSshKnownHostsContent = (
  205. await fs.promises.readFile(actualSshKnownHostsPath)
  206. ).toString()
  207. expect(actualSshKnownHostsContent).toMatch(
  208. /some-domain\.com ssh-rsa ABCDEF/
  209. )
  210. expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
  211. })
  212. const configureAuth_registersBasicCredentialAsSecret =
  213. 'configureAuth registers basic credential as secret'
  214. it(configureAuth_registersBasicCredentialAsSecret, async () => {
  215. // Arrange
  216. await setup(configureAuth_registersBasicCredentialAsSecret)
  217. expect(settings.authToken).toBeTruthy() // sanity check
  218. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  219. // Act
  220. await authHelper.configureAuth()
  221. // Assert secret
  222. const setSecretSpy = core.setSecret as jest.Mock<any>
  223. expect(setSecretSpy).toHaveBeenCalledTimes(1)
  224. const expectedSecret = Buffer.from(
  225. `x-access-token:${settings.authToken}`,
  226. 'utf8'
  227. ).toString('base64')
  228. expect(setSecretSpy).toHaveBeenCalledWith(expectedSecret)
  229. })
  230. const setsSshCommandEnvVarWhenPersistCredentialsFalse =
  231. 'sets SSH command env var when persist-credentials false'
  232. it(setsSshCommandEnvVarWhenPersistCredentialsFalse, async () => {
  233. if (!sshPath) {
  234. process.stdout.write(
  235. `Skipped test "${setsSshCommandEnvVarWhenPersistCredentialsFalse}". Executable 'ssh' not found in the PATH.\n`
  236. )
  237. return
  238. }
  239. // Arrange
  240. await setup(setsSshCommandEnvVarWhenPersistCredentialsFalse)
  241. settings.persistCredentials = false
  242. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  243. // Act
  244. await authHelper.configureAuth()
  245. // Assert git env var
  246. const actualKeyPath = await getActualSshKeyPath()
  247. const actualKnownHostsPath = await getActualSshKnownHostsPath()
  248. const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
  249. actualKeyPath
  250. )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
  251. actualKnownHostsPath
  252. )}"`
  253. expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
  254. 'GIT_SSH_COMMAND',
  255. expectedSshCommand
  256. )
  257. // Assert git config
  258. const gitConfigLines = (await fs.promises.readFile(localGitConfigPath))
  259. .toString()
  260. .split('\n')
  261. .filter(x => x)
  262. // Should have includeIf entries pointing to credentials file
  263. expect(gitConfigLines.length).toBeGreaterThan(0)
  264. expect(
  265. gitConfigLines.some(line => line.indexOf('includeIf.gitdir:') >= 0)
  266. ).toBeTruthy()
  267. })
  268. const configureAuth_setsSshCommandWhenPersistCredentialsTrue =
  269. 'sets SSH command when persist-credentials true'
  270. it(configureAuth_setsSshCommandWhenPersistCredentialsTrue, async () => {
  271. if (!sshPath) {
  272. process.stdout.write(
  273. `Skipped test "${configureAuth_setsSshCommandWhenPersistCredentialsTrue}". Executable 'ssh' not found in the PATH.\n`
  274. )
  275. return
  276. }
  277. // Arrange
  278. await setup(configureAuth_setsSshCommandWhenPersistCredentialsTrue)
  279. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  280. // Act
  281. await authHelper.configureAuth()
  282. // Assert git env var
  283. const actualKeyPath = await getActualSshKeyPath()
  284. const actualKnownHostsPath = await getActualSshKnownHostsPath()
  285. const expectedSshCommand = `"${sshPath}" -i "$RUNNER_TEMP/${path.basename(
  286. actualKeyPath
  287. )}" -o StrictHostKeyChecking=yes -o CheckHostIP=no -o "UserKnownHostsFile=$RUNNER_TEMP/${path.basename(
  288. actualKnownHostsPath
  289. )}"`
  290. expect(git.setEnvironmentVariable).toHaveBeenCalledWith(
  291. 'GIT_SSH_COMMAND',
  292. expectedSshCommand
  293. )
  294. // Asserty git config
  295. expect(git.config).toHaveBeenCalledWith(
  296. 'core.sshCommand',
  297. expectedSshCommand
  298. )
  299. })
  300. const configureAuth_writesExplicitKnownHosts = 'writes explicit known hosts'
  301. it(configureAuth_writesExplicitKnownHosts, async () => {
  302. if (!sshPath) {
  303. process.stdout.write(
  304. `Skipped test "${configureAuth_writesExplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
  305. )
  306. return
  307. }
  308. // Arrange
  309. await setup(configureAuth_writesExplicitKnownHosts)
  310. expect(settings.sshKey).toBeTruthy() // sanity check
  311. settings.sshKnownHosts = 'my-custom-host.com ssh-rsa ABC123'
  312. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  313. // Act
  314. await authHelper.configureAuth()
  315. // Assert known hosts
  316. const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
  317. const actualSshKnownHostsContent = (
  318. await fs.promises.readFile(actualSshKnownHostsPath)
  319. ).toString()
  320. expect(actualSshKnownHostsContent).toMatch(
  321. /my-custom-host\.com ssh-rsa ABC123/
  322. )
  323. expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
  324. })
  325. const configureAuth_writesSshKeyAndImplicitKnownHosts =
  326. 'writes SSH key and implicit known hosts'
  327. it(configureAuth_writesSshKeyAndImplicitKnownHosts, async () => {
  328. if (!sshPath) {
  329. process.stdout.write(
  330. `Skipped test "${configureAuth_writesSshKeyAndImplicitKnownHosts}". Executable 'ssh' not found in the PATH.\n`
  331. )
  332. return
  333. }
  334. // Arrange
  335. await setup(configureAuth_writesSshKeyAndImplicitKnownHosts)
  336. expect(settings.sshKey).toBeTruthy() // sanity check
  337. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  338. // Act
  339. await authHelper.configureAuth()
  340. // Assert SSH key
  341. const actualSshKeyPath = await getActualSshKeyPath()
  342. expect(actualSshKeyPath).toBeTruthy()
  343. const actualSshKeyContent = (
  344. await fs.promises.readFile(actualSshKeyPath)
  345. ).toString()
  346. expect(actualSshKeyContent).toBe(settings.sshKey + '\n')
  347. if (!isWindows) {
  348. // Assert read/write for user, not group or others.
  349. // Otherwise SSH client will error.
  350. expect((await fs.promises.stat(actualSshKeyPath)).mode & 0o777).toBe(
  351. 0o600
  352. )
  353. }
  354. // Assert known hosts
  355. const actualSshKnownHostsPath = await getActualSshKnownHostsPath()
  356. const actualSshKnownHostsContent = (
  357. await fs.promises.readFile(actualSshKnownHostsPath)
  358. ).toString()
  359. expect(actualSshKnownHostsContent).toMatch(/github\.com ssh-rsa AAAAB3N/)
  360. })
  361. const configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet =
  362. 'configureGlobalAuth configures URL insteadOf when SSH key not set'
  363. it(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet, async () => {
  364. // Arrange
  365. await setup(configureGlobalAuth_configuresUrlInsteadOfWhenSshKeyNotSet)
  366. settings.sshKey = ''
  367. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  368. // Act
  369. await authHelper.configureAuth()
  370. await authHelper.configureGlobalAuth()
  371. // Assert temporary global config
  372. expect(git.env['HOME']).toBeTruthy()
  373. const configContent = (
  374. await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
  375. ).toString()
  376. expect(
  377. configContent.indexOf(`url.https://github.com/.insteadOf git@github.com`)
  378. ).toBeGreaterThanOrEqual(0)
  379. })
  380. const configureGlobalAuth_copiesGlobalGitConfig =
  381. 'configureGlobalAuth copies global git config'
  382. it(configureGlobalAuth_copiesGlobalGitConfig, async () => {
  383. // Arrange
  384. await setup(configureGlobalAuth_copiesGlobalGitConfig)
  385. await fs.promises.writeFile(globalGitConfigPath, 'value-from-global-config')
  386. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  387. // Act
  388. await authHelper.configureAuth()
  389. await authHelper.configureGlobalAuth()
  390. // Assert original global config not altered
  391. let configContent = (
  392. await fs.promises.readFile(globalGitConfigPath)
  393. ).toString()
  394. expect(configContent).toBe('value-from-global-config')
  395. // Assert temporary global config
  396. expect(git.env['HOME']).toBeTruthy()
  397. const basicCredential = Buffer.from(
  398. `x-access-token:${settings.authToken}`,
  399. 'utf8'
  400. ).toString('base64')
  401. configContent = (
  402. await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
  403. ).toString()
  404. expect(
  405. configContent.indexOf('value-from-global-config')
  406. ).toBeGreaterThanOrEqual(0)
  407. // Global config should have include.path pointing to credentials file
  408. expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
  409. // Check credentials in the separate config file
  410. const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  411. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  412. )
  413. expect(credentialsFiles.length).toBeGreaterThan(0)
  414. const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
  415. const credentialsContent = (
  416. await fs.promises.readFile(credentialsConfigPath)
  417. ).toString()
  418. expect(
  419. credentialsContent.indexOf(
  420. `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
  421. )
  422. ).toBeGreaterThanOrEqual(0)
  423. })
  424. const configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist =
  425. 'configureGlobalAuth creates new git config when global does not exist'
  426. it(
  427. configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist,
  428. async () => {
  429. // Arrange
  430. await setup(
  431. configureGlobalAuth_createsNewGlobalGitConfigWhenGlobalDoesNotExist
  432. )
  433. await io.rmRF(globalGitConfigPath)
  434. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  435. // Act
  436. await authHelper.configureAuth()
  437. await authHelper.configureGlobalAuth()
  438. // Assert original global config not recreated
  439. try {
  440. await fs.promises.stat(globalGitConfigPath)
  441. throw new Error(
  442. `Did not expect file to exist: '${globalGitConfigPath}'`
  443. )
  444. } catch (err) {
  445. if ((err as any)?.code !== 'ENOENT') {
  446. throw err
  447. }
  448. }
  449. // Assert temporary global config
  450. expect(git.env['HOME']).toBeTruthy()
  451. const basicCredential = Buffer.from(
  452. `x-access-token:${settings.authToken}`,
  453. 'utf8'
  454. ).toString('base64')
  455. const configContent = (
  456. await fs.promises.readFile(path.join(git.env['HOME'], '.gitconfig'))
  457. ).toString()
  458. // Global config should have include.path pointing to credentials file
  459. expect(configContent.indexOf('include.path')).toBeGreaterThanOrEqual(0)
  460. // Check credentials in the separate config file
  461. const credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  462. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  463. )
  464. expect(credentialsFiles.length).toBeGreaterThan(0)
  465. const credentialsConfigPath = path.join(runnerTemp, credentialsFiles[0])
  466. const credentialsContent = (
  467. await fs.promises.readFile(credentialsConfigPath)
  468. ).toString()
  469. expect(
  470. credentialsContent.indexOf(
  471. `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
  472. )
  473. ).toBeGreaterThanOrEqual(0)
  474. }
  475. )
  476. const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet =
  477. 'configureSubmoduleAuth configures submodules when persist credentials false and SSH key not set'
  478. it(
  479. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet,
  480. async () => {
  481. // Arrange
  482. await setup(
  483. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeyNotSet
  484. )
  485. settings.persistCredentials = false
  486. settings.sshKey = ''
  487. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  488. await authHelper.configureAuth()
  489. const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
  490. mockSubmoduleForeach.mockClear() // reset calls
  491. // Act
  492. await authHelper.configureSubmoduleAuth()
  493. // Assert
  494. expect(mockSubmoduleForeach).toBeCalledTimes(1)
  495. expect(mockSubmoduleForeach.mock.calls[0][0] as string).toMatch(
  496. /unset-all.*insteadOf/
  497. )
  498. }
  499. )
  500. const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet =
  501. 'configureSubmoduleAuth configures submodules when persist credentials false and SSH key set'
  502. it(
  503. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet,
  504. async () => {
  505. if (!sshPath) {
  506. process.stdout.write(
  507. `Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
  508. )
  509. return
  510. }
  511. // Arrange
  512. await setup(
  513. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsFalseAndSshKeySet
  514. )
  515. settings.persistCredentials = false
  516. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  517. await authHelper.configureAuth()
  518. const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
  519. mockSubmoduleForeach.mockClear() // reset calls
  520. // Act
  521. await authHelper.configureSubmoduleAuth()
  522. // Assert
  523. expect(mockSubmoduleForeach).toHaveBeenCalledTimes(1)
  524. expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
  525. /unset-all.*insteadOf/
  526. )
  527. }
  528. )
  529. const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet =
  530. 'configureSubmoduleAuth configures submodules when persist credentials true and SSH key not set'
  531. it(
  532. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet,
  533. async () => {
  534. // Arrange
  535. await setup(
  536. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeyNotSet
  537. )
  538. settings.sshKey = ''
  539. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  540. await authHelper.configureAuth()
  541. const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
  542. mockSubmoduleForeach.mockClear() // reset calls
  543. // Act
  544. await authHelper.configureSubmoduleAuth()
  545. // Assert
  546. // Should configure insteadOf (2 calls for two values)
  547. expect(mockSubmoduleForeach).toHaveBeenCalledTimes(3)
  548. expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
  549. /unset-all.*insteadOf/
  550. )
  551. expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(
  552. /url.*insteadOf.*git@github.com:/
  553. )
  554. expect(mockSubmoduleForeach.mock.calls[2][0]).toMatch(
  555. /url.*insteadOf.*org-123456@github.com:/
  556. )
  557. }
  558. )
  559. const configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet =
  560. 'configureSubmoduleAuth configures submodules when persist credentials true and SSH key set'
  561. it(
  562. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet,
  563. async () => {
  564. if (!sshPath) {
  565. process.stdout.write(
  566. `Skipped test "${configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet}". Executable 'ssh' not found in the PATH.\n`
  567. )
  568. return
  569. }
  570. // Arrange
  571. await setup(
  572. configureSubmoduleAuth_configuresSubmodulesWhenPersistCredentialsTrueAndSshKeySet
  573. )
  574. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  575. await authHelper.configureAuth()
  576. const mockSubmoduleForeach = git.submoduleForeach as jest.Mock<any>
  577. mockSubmoduleForeach.mockClear() // reset calls
  578. // Act
  579. await authHelper.configureSubmoduleAuth()
  580. // Assert
  581. // Should configure sshCommand (1 call)
  582. expect(mockSubmoduleForeach).toHaveBeenCalledTimes(2)
  583. expect(mockSubmoduleForeach.mock.calls[0][0]).toMatch(
  584. /unset-all.*insteadOf/
  585. )
  586. expect(mockSubmoduleForeach.mock.calls[1][0]).toMatch(/core\.sshCommand/)
  587. }
  588. )
  589. const removeAuth_removesSshCommand = 'removeAuth removes SSH command'
  590. it(removeAuth_removesSshCommand, async () => {
  591. if (!sshPath) {
  592. process.stdout.write(
  593. `Skipped test "${removeAuth_removesSshCommand}". Executable 'ssh' not found in the PATH.\n`
  594. )
  595. return
  596. }
  597. // Arrange
  598. await setup(removeAuth_removesSshCommand)
  599. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  600. await authHelper.configureAuth()
  601. let gitConfigContent = (
  602. await fs.promises.readFile(localGitConfigPath)
  603. ).toString()
  604. expect(gitConfigContent.indexOf('core.sshCommand')).toBeGreaterThanOrEqual(
  605. 0
  606. ) // sanity check
  607. const actualKeyPath = await getActualSshKeyPath()
  608. expect(actualKeyPath).toBeTruthy()
  609. await fs.promises.stat(actualKeyPath)
  610. const actualKnownHostsPath = await getActualSshKnownHostsPath()
  611. expect(actualKnownHostsPath).toBeTruthy()
  612. await fs.promises.stat(actualKnownHostsPath)
  613. // Act
  614. await authHelper.removeAuth()
  615. // Assert git config
  616. gitConfigContent = (
  617. await fs.promises.readFile(localGitConfigPath)
  618. ).toString()
  619. expect(gitConfigContent.indexOf('core.sshCommand')).toBeLessThan(0)
  620. // Assert SSH key file
  621. try {
  622. await fs.promises.stat(actualKeyPath)
  623. throw new Error('SSH key should have been deleted')
  624. } catch (err) {
  625. if ((err as any)?.code !== 'ENOENT') {
  626. throw err
  627. }
  628. }
  629. // Assert known hosts file
  630. try {
  631. await fs.promises.stat(actualKnownHostsPath)
  632. throw new Error('SSH known hosts should have been deleted')
  633. } catch (err) {
  634. if ((err as any)?.code !== 'ENOENT') {
  635. throw err
  636. }
  637. }
  638. })
  639. const removeAuth_removesToken = 'removeAuth removes token'
  640. it(removeAuth_removesToken, async () => {
  641. // Arrange
  642. await setup(removeAuth_removesToken)
  643. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  644. await authHelper.configureAuth()
  645. // Verify includeIf entries exist in local config
  646. let localConfigContent = (
  647. await fs.promises.readFile(localGitConfigPath)
  648. ).toString()
  649. expect(
  650. localConfigContent.indexOf('includeIf.gitdir:')
  651. ).toBeGreaterThanOrEqual(0)
  652. // Verify both host and container includeIf entries are present
  653. const hostGitDir = path.join(workspace, '.git').replace(/\\/g, '/')
  654. expect(
  655. localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`)
  656. ).toBeGreaterThanOrEqual(0)
  657. expect(
  658. localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path')
  659. ).toBeGreaterThanOrEqual(0)
  660. // Verify credentials file exists
  661. let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  662. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  663. )
  664. expect(credentialsFiles.length).toBe(1)
  665. const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0])
  666. // Verify credentials file contains the auth token
  667. let credentialsContent = (
  668. await fs.promises.readFile(credentialsFilePath)
  669. ).toString()
  670. const basicCredential = Buffer.from(
  671. `x-access-token:${settings.authToken}`,
  672. 'utf8'
  673. ).toString('base64')
  674. expect(
  675. credentialsContent.indexOf(
  676. `http.https://github.com/.extraheader AUTHORIZATION: basic ${basicCredential}`
  677. )
  678. ).toBeGreaterThanOrEqual(0)
  679. // Verify the includeIf entries point to the credentials file
  680. const containerCredentialsPath = path.posix.join(
  681. '/github/runner_temp',
  682. path.basename(credentialsFilePath)
  683. )
  684. expect(
  685. localConfigContent.indexOf(credentialsFilePath)
  686. ).toBeGreaterThanOrEqual(0)
  687. expect(
  688. localConfigContent.indexOf(containerCredentialsPath)
  689. ).toBeGreaterThanOrEqual(0)
  690. // Act
  691. await authHelper.removeAuth()
  692. // Assert all includeIf entries removed from local git config
  693. localConfigContent = (
  694. await fs.promises.readFile(localGitConfigPath)
  695. ).toString()
  696. expect(localConfigContent.indexOf('includeIf.gitdir:')).toBeLessThan(0)
  697. expect(
  698. localConfigContent.indexOf(`includeIf.gitdir:${hostGitDir}.path`)
  699. ).toBeLessThan(0)
  700. expect(
  701. localConfigContent.indexOf('includeIf.gitdir:/github/workspace/.git.path')
  702. ).toBeLessThan(0)
  703. expect(localConfigContent.indexOf(credentialsFilePath)).toBeLessThan(0)
  704. expect(localConfigContent.indexOf(containerCredentialsPath)).toBeLessThan(0)
  705. // Assert credentials config file deleted
  706. credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  707. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  708. )
  709. expect(credentialsFiles.length).toBe(0)
  710. // Verify credentials file no longer exists on disk
  711. try {
  712. await fs.promises.stat(credentialsFilePath)
  713. throw new Error('Credentials file should have been deleted')
  714. } catch (err) {
  715. if ((err as any)?.code !== 'ENOENT') {
  716. throw err
  717. }
  718. }
  719. })
  720. const removeAuth_removesTokenFromSubmodules =
  721. 'removeAuth removes token from submodules'
  722. it(removeAuth_removesTokenFromSubmodules, async () => {
  723. // Arrange
  724. await setup(removeAuth_removesTokenFromSubmodules)
  725. // Create fake submodule config paths
  726. const submodule1Dir = path.join(workspace, '.git', 'modules', 'submodule-1')
  727. const submodule2Dir = path.join(workspace, '.git', 'modules', 'submodule-2')
  728. const submodule1ConfigPath = path.join(submodule1Dir, 'config')
  729. const submodule2ConfigPath = path.join(submodule2Dir, 'config')
  730. await fs.promises.mkdir(submodule1Dir, {recursive: true})
  731. await fs.promises.mkdir(submodule2Dir, {recursive: true})
  732. await fs.promises.writeFile(submodule1ConfigPath, '')
  733. await fs.promises.writeFile(submodule2ConfigPath, '')
  734. // Mock getSubmoduleConfigPaths to return our fake submodules (for both configure and remove)
  735. const mockGetSubmoduleConfigPaths =
  736. git.getSubmoduleConfigPaths as jest.Mock<any>
  737. mockGetSubmoduleConfigPaths.mockResolvedValue([
  738. submodule1ConfigPath,
  739. submodule2ConfigPath
  740. ])
  741. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  742. await authHelper.configureAuth()
  743. await authHelper.configureSubmoduleAuth()
  744. // Verify credentials file exists
  745. let credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  746. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  747. )
  748. expect(credentialsFiles.length).toBe(1)
  749. const credentialsFilePath = path.join(runnerTemp, credentialsFiles[0])
  750. // Verify submodule 1 config has includeIf entries
  751. let submodule1Content = (
  752. await fs.promises.readFile(submodule1ConfigPath)
  753. ).toString()
  754. const submodule1GitDir = submodule1Dir.replace(/\\/g, '/')
  755. expect(
  756. submodule1Content.indexOf(`includeIf.gitdir:${submodule1GitDir}.path`)
  757. ).toBeGreaterThanOrEqual(0)
  758. expect(
  759. submodule1Content.indexOf(credentialsFilePath)
  760. ).toBeGreaterThanOrEqual(0)
  761. // Verify submodule 2 config has includeIf entries
  762. let submodule2Content = (
  763. await fs.promises.readFile(submodule2ConfigPath)
  764. ).toString()
  765. const submodule2GitDir = submodule2Dir.replace(/\\/g, '/')
  766. expect(
  767. submodule2Content.indexOf(`includeIf.gitdir:${submodule2GitDir}.path`)
  768. ).toBeGreaterThanOrEqual(0)
  769. expect(
  770. submodule2Content.indexOf(credentialsFilePath)
  771. ).toBeGreaterThanOrEqual(0)
  772. // Verify both host and container paths are in each submodule config
  773. const containerCredentialsPath = path.posix.join(
  774. '/github/runner_temp',
  775. path.basename(credentialsFilePath)
  776. )
  777. expect(
  778. submodule1Content.indexOf(containerCredentialsPath)
  779. ).toBeGreaterThanOrEqual(0)
  780. expect(
  781. submodule2Content.indexOf(containerCredentialsPath)
  782. ).toBeGreaterThanOrEqual(0)
  783. // Act - ensure mock persists for removeAuth
  784. mockGetSubmoduleConfigPaths.mockResolvedValue([
  785. submodule1ConfigPath,
  786. submodule2ConfigPath
  787. ])
  788. await authHelper.removeAuth()
  789. // Assert submodule 1 includeIf entries removed
  790. submodule1Content = (
  791. await fs.promises.readFile(submodule1ConfigPath)
  792. ).toString()
  793. expect(submodule1Content.indexOf('includeIf.gitdir:')).toBeLessThan(0)
  794. expect(submodule1Content.indexOf(credentialsFilePath)).toBeLessThan(0)
  795. expect(submodule1Content.indexOf(containerCredentialsPath)).toBeLessThan(0)
  796. // Assert submodule 2 includeIf entries removed
  797. submodule2Content = (
  798. await fs.promises.readFile(submodule2ConfigPath)
  799. ).toString()
  800. expect(submodule2Content.indexOf('includeIf.gitdir:')).toBeLessThan(0)
  801. expect(submodule2Content.indexOf(credentialsFilePath)).toBeLessThan(0)
  802. expect(submodule2Content.indexOf(containerCredentialsPath)).toBeLessThan(0)
  803. // Assert credentials config file deleted
  804. credentialsFiles = (await fs.promises.readdir(runnerTemp)).filter(
  805. f => f.startsWith('git-credentials-') && f.endsWith('.config')
  806. )
  807. expect(credentialsFiles.length).toBe(0)
  808. // Verify credentials file no longer exists on disk
  809. try {
  810. await fs.promises.stat(credentialsFilePath)
  811. throw new Error('Credentials file should have been deleted')
  812. } catch (err) {
  813. if ((err as any)?.code !== 'ENOENT') {
  814. throw err
  815. }
  816. }
  817. })
  818. const removeGlobalConfig_removesOverride =
  819. 'removeGlobalConfig removes override'
  820. it(removeGlobalConfig_removesOverride, async () => {
  821. // Arrange
  822. await setup(removeGlobalConfig_removesOverride)
  823. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  824. await authHelper.configureAuth()
  825. await authHelper.configureGlobalAuth()
  826. const homeOverride = git.env['HOME'] // Sanity check
  827. expect(homeOverride).toBeTruthy()
  828. await fs.promises.stat(path.join(git.env['HOME'], '.gitconfig'))
  829. // Act
  830. await authHelper.removeGlobalConfig()
  831. // Assert
  832. expect(git.env['HOME']).toBeUndefined()
  833. try {
  834. await fs.promises.stat(homeOverride)
  835. throw new Error(`Should have been deleted '${homeOverride}'`)
  836. } catch (err) {
  837. if ((err as any)?.code !== 'ENOENT') {
  838. throw err
  839. }
  840. }
  841. })
  842. const testCredentialsConfigPath_matchesCredentialsConfigPaths =
  843. 'testCredentialsConfigPath matches credentials config paths'
  844. it(testCredentialsConfigPath_matchesCredentialsConfigPaths, async () => {
  845. // Arrange
  846. await setup(testCredentialsConfigPath_matchesCredentialsConfigPaths)
  847. const authHelper = gitAuthHelper.createAuthHelper(git, settings)
  848. // Get a real credentials config path
  849. const credentialsConfigPath = await (
  850. authHelper as any
  851. ).getCredentialsConfigPath()
  852. // Act & Assert
  853. expect(
  854. (authHelper as any).testCredentialsConfigPath(credentialsConfigPath)
  855. ).toBe(true)
  856. expect(
  857. (authHelper as any).testCredentialsConfigPath(
  858. '/some/path/git-credentials-12345678-abcd-1234-5678-123456789012.config'
  859. )
  860. ).toBe(true)
  861. expect(
  862. (authHelper as any).testCredentialsConfigPath(
  863. '/some/path/git-credentials-abcdef12-3456-7890-abcd-ef1234567890.config'
  864. )
  865. ).toBe(true)
  866. // Test invalid paths
  867. expect(
  868. (authHelper as any).testCredentialsConfigPath(
  869. '/some/path/other-config.config'
  870. )
  871. ).toBe(false)
  872. expect(
  873. (authHelper as any).testCredentialsConfigPath(
  874. '/some/path/git-credentials-invalid.config'
  875. )
  876. ).toBe(false)
  877. expect(
  878. (authHelper as any).testCredentialsConfigPath(
  879. '/some/path/git-credentials-.config'
  880. )
  881. ).toBe(false)
  882. expect((authHelper as any).testCredentialsConfigPath('')).toBe(false)
  883. })
  884. })
  885. async function setup(testName: string): Promise<void> {
  886. testName = testName.replace(/[^a-zA-Z0-9_]+/g, '-')
  887. // Directories
  888. workspace = path.join(testWorkspace, testName, 'workspace')
  889. runnerTemp = path.join(testWorkspace, testName, 'runner-temp')
  890. tempHomedir = path.join(testWorkspace, testName, 'home-dir')
  891. await fs.promises.mkdir(workspace, {recursive: true})
  892. await fs.promises.mkdir(runnerTemp, {recursive: true})
  893. await fs.promises.mkdir(tempHomedir, {recursive: true})
  894. process.env['RUNNER_TEMP'] = runnerTemp
  895. process.env['HOME'] = tempHomedir
  896. process.env['GITHUB_WORKSPACE'] = workspace
  897. // Create git config
  898. globalGitConfigPath = path.join(tempHomedir, '.gitconfig')
  899. await fs.promises.writeFile(globalGitConfigPath, '')
  900. localGitConfigPath = path.join(workspace, '.git', 'config')
  901. await fs.promises.mkdir(path.dirname(localGitConfigPath), {recursive: true})
  902. await fs.promises.writeFile(localGitConfigPath, '')
  903. git = {
  904. branchDelete: jest.fn(),
  905. branchExists: jest.fn(),
  906. branchList: jest.fn(),
  907. disableSparseCheckout: jest.fn(),
  908. sparseCheckout: jest.fn(),
  909. sparseCheckoutNonConeMode: jest.fn(),
  910. checkout: jest.fn(),
  911. checkoutDetach: jest.fn(),
  912. config: jest.fn(
  913. async (
  914. key: string,
  915. value: string,
  916. globalConfig?: boolean,
  917. add?: boolean,
  918. configFile?: string
  919. ) => {
  920. const configPath =
  921. configFile ||
  922. (globalConfig
  923. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  924. : localGitConfigPath)
  925. // Ensure directory exists
  926. await fs.promises.mkdir(path.dirname(configPath), {recursive: true})
  927. await fs.promises.appendFile(configPath, `\n${key} ${value}`)
  928. }
  929. ),
  930. configExists: jest.fn(
  931. async (key: string, globalConfig?: boolean): Promise<boolean> => {
  932. const configPath = globalConfig
  933. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  934. : localGitConfigPath
  935. const content = await fs.promises.readFile(configPath)
  936. const lines = content
  937. .toString()
  938. .split('\n')
  939. .filter(x => x)
  940. return lines.some(x => x.startsWith(key))
  941. }
  942. ),
  943. env: {},
  944. fetch: jest.fn(),
  945. getDefaultBranch: jest.fn(),
  946. getSubmoduleConfigPaths: jest.fn(async () => []),
  947. getWorkingDirectory: jest.fn(() => workspace),
  948. init: jest.fn(),
  949. isDetached: jest.fn(),
  950. lfsFetch: jest.fn(),
  951. lfsInstall: jest.fn(),
  952. log1: jest.fn(),
  953. remoteAdd: jest.fn(),
  954. removeEnvironmentVariable: jest.fn((name: string) => delete git.env[name]),
  955. revParse: jest.fn(),
  956. setEnvironmentVariable: jest.fn((name: string, value: string) => {
  957. git.env[name] = value
  958. }),
  959. shaExists: jest.fn(),
  960. submoduleForeach: jest.fn(async () => {
  961. return ''
  962. }),
  963. submoduleSync: jest.fn(),
  964. submoduleStatus: jest.fn(async () => {
  965. return true
  966. }),
  967. submoduleUpdate: jest.fn(),
  968. tagExists: jest.fn(),
  969. tryClean: jest.fn(),
  970. tryConfigUnset: jest.fn(
  971. async (key: string, globalConfig?: boolean): Promise<boolean> => {
  972. const configPath = globalConfig
  973. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  974. : localGitConfigPath
  975. let content = await fs.promises.readFile(configPath)
  976. let lines = content
  977. .toString()
  978. .split('\n')
  979. .filter(x => x)
  980. .filter(x => !x.startsWith(key))
  981. await fs.promises.writeFile(configPath, lines.join('\n'))
  982. return true
  983. }
  984. ),
  985. tryConfigUnsetValue: jest.fn(
  986. async (
  987. key: string,
  988. value: string,
  989. globalConfig?: boolean,
  990. configPath?: string
  991. ): Promise<boolean> => {
  992. const targetConfigPath =
  993. configPath ||
  994. (globalConfig
  995. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  996. : localGitConfigPath)
  997. let content = await fs.promises.readFile(targetConfigPath)
  998. let lines = content
  999. .toString()
  1000. .split('\n')
  1001. .filter(x => x)
  1002. .filter(x => !(x.startsWith(key) && x.includes(value)))
  1003. await fs.promises.writeFile(targetConfigPath, lines.join('\n'))
  1004. return true
  1005. }
  1006. ),
  1007. tryDisableAutomaticGarbageCollection: jest.fn(),
  1008. tryGetFetchUrl: jest.fn(),
  1009. tryGetConfigValues: jest.fn(
  1010. async (
  1011. key: string,
  1012. globalConfig?: boolean,
  1013. configPath?: string
  1014. ): Promise<string[]> => {
  1015. const targetConfigPath =
  1016. configPath ||
  1017. (globalConfig
  1018. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  1019. : localGitConfigPath)
  1020. const content = await fs.promises.readFile(targetConfigPath)
  1021. const lines = content
  1022. .toString()
  1023. .split('\n')
  1024. .filter(x => x && x.startsWith(key))
  1025. .map(x => x.substring(key.length).trim())
  1026. return lines
  1027. }
  1028. ),
  1029. tryGetConfigKeys: jest.fn(
  1030. async (
  1031. pattern: string,
  1032. globalConfig?: boolean,
  1033. configPath?: string
  1034. ): Promise<string[]> => {
  1035. const targetConfigPath =
  1036. configPath ||
  1037. (globalConfig
  1038. ? path.join(git.env['HOME'] || tempHomedir, '.gitconfig')
  1039. : localGitConfigPath)
  1040. const content = await fs.promises.readFile(targetConfigPath)
  1041. const lines = content
  1042. .toString()
  1043. .split('\n')
  1044. .filter(x => x)
  1045. const keys = lines
  1046. .filter(x => new RegExp(pattern).test(x.split(' ')[0]))
  1047. .map(x => x.split(' ')[0])
  1048. return [...new Set(keys)] // Remove duplicates
  1049. }
  1050. ),
  1051. tryReset: jest.fn(),
  1052. version: jest.fn()
  1053. } as unknown as IGitCommandManager & {env: {[key: string]: string}}
  1054. settings = {
  1055. authToken: 'some auth token',
  1056. clean: true,
  1057. commit: '',
  1058. filter: undefined,
  1059. sparseCheckout: [],
  1060. sparseCheckoutConeMode: true,
  1061. fetchDepth: 1,
  1062. fetchTags: false,
  1063. showProgress: true,
  1064. lfs: false,
  1065. submodules: false,
  1066. nestedSubmodules: false,
  1067. persistCredentials: true,
  1068. ref: 'refs/heads/main',
  1069. repositoryName: 'my-repo',
  1070. repositoryOwner: 'my-org',
  1071. repositoryPath: '',
  1072. sshKey: sshPath ? 'some ssh private key' : '',
  1073. sshKnownHosts: '',
  1074. sshStrict: true,
  1075. sshUser: '',
  1076. workflowOrganizationId: 123456,
  1077. setSafeDirectory: true,
  1078. githubServerUrl: githubServerUrl,
  1079. allowUnsafePrCheckout: false
  1080. }
  1081. }
  1082. async function getActualSshKeyPath(): Promise<string> {
  1083. let actualTempFiles = (await fs.promises.readdir(runnerTemp))
  1084. .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
  1085. .sort()
  1086. .map(x => path.join(runnerTemp, x))
  1087. if (actualTempFiles.length === 0) {
  1088. return ''
  1089. }
  1090. expect(actualTempFiles).toHaveLength(2)
  1091. expect(actualTempFiles[0].endsWith('_known_hosts')).toBeFalsy()
  1092. return actualTempFiles[0]
  1093. }
  1094. async function getActualSshKnownHostsPath(): Promise<string> {
  1095. let actualTempFiles = (await fs.promises.readdir(runnerTemp))
  1096. .filter(x => !x.startsWith('git-credentials-')) // Exclude credentials config file
  1097. .sort()
  1098. .map(x => path.join(runnerTemp, x))
  1099. if (actualTempFiles.length === 0) {
  1100. return ''
  1101. }
  1102. expect(actualTempFiles).toHaveLength(2)
  1103. expect(actualTempFiles[1].endsWith('_known_hosts')).toBeTruthy()
  1104. expect(actualTempFiles[1].startsWith(actualTempFiles[0])).toBeTruthy()
  1105. return actualTempFiles[1]
  1106. }