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 }