2022-02-23 15:58:18 +00:00
< ? php declare ( strict_types = 1 );
2019-11-06 23:43:19 +00:00
2020-11-22 13:48:56 +00:00
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2019-11-06 23:43:19 +00:00
namespace Composer\Test\Util ;
use Composer\Config ;
use Composer\IO\IOInterface ;
2024-10-02 12:36:30 +00:00
use Composer\Test\Mock\HttpDownloaderMock ;
2019-11-06 23:43:19 +00:00
use Composer\Util\Filesystem ;
use Composer\Util\Git ;
2021-08-19 11:00:30 +00:00
use Composer\Test\Mock\ProcessExecutorMock ;
2020-02-07 03:18:45 +00:00
use Composer\Test\TestCase ;
2019-11-06 23:43:19 +00:00
class GitTest extends TestCase
{
/** @var Git */
private $git ;
2021-03-09 14:49:40 +00:00
/** @var IOInterface&\PHPUnit\Framework\MockObject\MockObject */
2019-11-06 23:43:19 +00:00
private $io ;
2021-03-09 14:49:40 +00:00
/** @var Config&\PHPUnit\Framework\MockObject\MockObject */
2019-11-06 23:43:19 +00:00
private $config ;
2021-08-19 11:00:30 +00:00
/** @var ProcessExecutorMock */
2019-11-06 23:43:19 +00:00
private $process ;
2021-03-09 14:49:40 +00:00
/** @var Filesystem&\PHPUnit\Framework\MockObject\MockObject */
2019-11-06 23:43:19 +00:00
private $fs ;
2021-12-08 16:03:05 +00:00
protected function setUp () : void
2019-11-06 23:43:19 +00:00
{
$this -> io = $this -> getMockBuilder ( 'Composer\IO\IOInterface' ) -> getMock ();
$this -> config = $this -> getMockBuilder ( 'Composer\Config' ) -> disableOriginalConstructor () -> getMock ();
2021-12-09 16:09:07 +00:00
$this -> process = $this -> getProcessExecutorMock ();
2019-11-06 23:43:19 +00:00
$this -> fs = $this -> getMockBuilder ( 'Composer\Util\Filesystem' ) -> disableOriginalConstructor () -> getMock ();
$this -> git = new Git ( $this -> io , $this -> config , $this -> process , $this -> fs );
}
/**
* @ dataProvider publicGithubNoCredentialsProvider
*/
2022-02-22 15:47:09 +00:00
public function testRunCommandPublicGitHubRepositoryNotInitialClone ( string $protocol , string $expectedUrl ) : void
2019-11-06 23:43:19 +00:00
{
2022-02-21 12:42:28 +00:00
$commandCallable = function ( $url ) use ( $expectedUrl ) : string {
2024-05-29 21:12:06 +00:00
self :: assertSame ( $expectedUrl , $url );
2019-11-06 23:43:19 +00:00
return 'git command' ;
};
$this -> mockConfig ( $protocol );
2022-08-17 12:20:07 +00:00
$this -> process -> expects ([ 'git command' ], true );
2019-11-06 23:43:19 +00:00
2024-11-06 12:49:06 +00:00
// @phpstan-ignore method.deprecated
2019-11-06 23:43:19 +00:00
$this -> git -> runCommand ( $commandCallable , 'https://github.com/acme/repo' , null , true );
}
2022-11-24 13:39:08 +00:00
public static function publicGithubNoCredentialsProvider () : array
2019-11-06 23:43:19 +00:00
{
2022-08-17 12:20:07 +00:00
return [
[ 'ssh' , 'git@github.com:acme/repo' ],
[ 'https' , 'https://github.com/acme/repo' ],
];
2019-11-06 23:43:19 +00:00
}
2022-02-18 09:38:54 +00:00
public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteractiveWithoutAuthentication () : void
2019-11-06 23:43:19 +00:00
{
2021-12-09 19:55:26 +00:00
self :: expectException ( 'RuntimeException' );
2020-09-10 15:21:11 +00:00
2022-02-21 12:42:28 +00:00
$commandCallable = function ( $url ) : string {
2024-05-29 21:12:06 +00:00
self :: assertSame ( 'https://github.com/acme/repo' , $url );
2019-11-06 23:43:19 +00:00
return 'git command' ;
};
$this -> mockConfig ( 'https' );
2022-08-17 12:20:07 +00:00
$this -> process -> expects ([
[ 'cmd' => 'git command' , 'return' => 1 ],
2024-11-06 12:49:06 +00:00
[ 'cmd' => [ 'git' , '--version' ], 'return' => 0 ],
2022-08-17 12:20:07 +00:00
], true );
2019-11-06 23:43:19 +00:00
2024-11-06 12:49:06 +00:00
// @phpstan-ignore method.deprecated
2019-11-06 23:43:19 +00:00
$this -> git -> runCommand ( $commandCallable , 'https://github.com/acme/repo' , null , true );
}
/**
* @ dataProvider privateGithubWithCredentialsProvider
*/
2022-02-22 15:47:09 +00:00
public function testRunCommandPrivateGitHubRepositoryNotInitialCloneNotInteractiveWithAuthentication ( string $gitUrl , string $protocol , string $gitHubToken , string $expectedUrl , int $expectedFailuresBeforeSuccess ) : void
2019-11-06 23:43:19 +00:00
{
2022-08-17 12:20:07 +00:00
$commandCallable = static function ( $url ) use ( $expectedUrl ) : string {
2019-11-06 23:43:19 +00:00
if ( $url !== $expectedUrl ) {
return 'git command failing' ;
}
return 'git command ok' ;
};
$this -> mockConfig ( $protocol );
2022-08-17 12:20:07 +00:00
$expectedCalls = array_fill ( 0 , $expectedFailuresBeforeSuccess , [ 'cmd' => 'git command failing' , 'return' => 1 ]);
$expectedCalls [] = [ 'cmd' => 'git command ok' , 'return' => 0 ];
2021-08-19 11:00:30 +00:00
$this -> process -> expects ( $expectedCalls , true );
2019-11-06 23:43:19 +00:00
$this -> io
-> method ( 'isInteractive' )
-> willReturn ( false );
$this -> io
2020-02-07 06:35:07 +00:00
-> expects ( $this -> atLeastOnce ())
2019-11-06 23:43:19 +00:00
-> method ( 'hasAuthentication' )
-> with ( $this -> equalTo ( 'github.com' ))
-> willReturn ( true );
$this -> io
2020-02-07 06:35:07 +00:00
-> expects ( $this -> atLeastOnce ())
2019-11-06 23:43:19 +00:00
-> method ( 'getAuthentication' )
-> with ( $this -> equalTo ( 'github.com' ))
2022-08-17 12:20:07 +00:00
-> willReturn ([ 'username' => 'token' , 'password' => $gitHubToken ]);
2019-11-06 23:43:19 +00:00
2024-11-06 12:49:06 +00:00
// @phpstan-ignore method.deprecated
2019-11-06 23:43:19 +00:00
$this -> git -> runCommand ( $commandCallable , $gitUrl , null , true );
}
2024-10-02 12:36:30 +00:00
/**
* @ dataProvider privateBitbucketWithCredentialsProvider
*/
public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneNotInteractiveWithAuthentication ( string $gitUrl , ? string $bitbucketToken , string $expectedUrl , int $expectedFailuresBeforeSuccess , int $bitbucket_git_auth_calls = 0 ) : void
{
$commandCallable = static function ( $url ) use ( $expectedUrl ) : string {
if ( $url !== $expectedUrl ) {
return 'git command failing' ;
}
return 'git command ok' ;
};
$this -> config
-> method ( 'get' )
-> willReturnMap ([
[ 'gitlab-domains' , 0 , [ 'gitlab.com' ]],
[ 'github-domains' , 0 , [ 'github.com' ]],
]);
$expectedCalls = array_fill ( 0 , $expectedFailuresBeforeSuccess , [ 'cmd' => 'git command failing' , 'return' => 1 ]);
if ( $bitbucket_git_auth_calls > 0 ) {
// When we are testing what happens without auth saved, and URLs
// with https, there will also be an attempt to find the token in
// the git config for the folder and repo, locally.
2024-11-06 12:49:06 +00:00
$additional_calls = array_fill ( 0 , $bitbucket_git_auth_calls , [ 'cmd' => [ 'git' , 'config' , 'bitbucket.accesstoken' ], 'return' => 1 ]);
2024-10-02 12:36:30 +00:00
foreach ( $additional_calls as $call ) {
$expectedCalls [] = $call ;
}
}
$expectedCalls [] = [ 'cmd' => 'git command ok' , 'return' => 0 ];
$this -> process -> expects ( $expectedCalls , true );
$this -> io
-> method ( 'isInteractive' )
-> willReturn ( false );
if ( null !== $bitbucketToken ) {
$this -> io
-> expects ( $this -> atLeastOnce ())
-> method ( 'hasAuthentication' )
-> with ( $this -> equalTo ( 'bitbucket.org' ))
-> willReturn ( true );
$this -> io
-> expects ( $this -> atLeastOnce ())
-> method ( 'getAuthentication' )
-> with ( $this -> equalTo ( 'bitbucket.org' ))
-> willReturn ([ 'username' => 'token' , 'password' => $bitbucketToken ]);
}
2024-11-06 12:49:06 +00:00
// @phpstan-ignore method.deprecated
2024-10-02 12:36:30 +00:00
$this -> git -> runCommand ( $commandCallable , $gitUrl , null , true );
}
/**
* @ dataProvider privateBitbucketWithOauthProvider
*
* @ param string $gitUrl
* @ param string $expectedUrl
* @ param array { 'username' : string , 'password' : string }[] $initial_config
*/
public function testRunCommandPrivateBitbucketRepositoryNotInitialCloneInteractiveWithOauth ( string $gitUrl , string $expectedUrl , array $initial_config = []) : void
{
$commandCallable = static function ( $url ) use ( $expectedUrl ) : string {
if ( $url !== $expectedUrl ) {
return 'git command failing' ;
}
return 'git command ok' ;
};
$expectedCalls = [];
$expectedCalls [] = [ 'cmd' => 'git command failing' , 'return' => 1 ];
if ( count ( $initial_config ) > 0 ) {
$expectedCalls [] = [ 'cmd' => 'git command failing' , 'return' => 1 ];
} else {
2024-11-06 12:49:06 +00:00
$expectedCalls [] = [ 'cmd' => [ 'git' , 'config' , 'bitbucket.accesstoken' ], 'return' => 1 ];
2024-10-02 12:36:30 +00:00
}
$expectedCalls [] = [ 'cmd' => 'git command ok' , 'return' => 0 ];
$this -> process -> expects ( $expectedCalls , true );
$this -> config
-> method ( 'get' )
-> willReturnMap ([
[ 'gitlab-domains' , 0 , [ 'gitlab.com' ]],
[ 'github-domains' , 0 , [ 'github.com' ]],
]);
$this -> io
-> method ( 'isInteractive' )
-> willReturn ( true );
$this -> io
-> method ( 'askConfirmation' )
-> willReturnCallback ( function () {
return true ;
});
$this -> io -> method ( 'askAndHideAnswer' )
-> willReturnCallback ( function ( $question ) {
switch ( $question ) {
case 'Consumer Key (hidden): ' :
return 'my-consumer-key' ;
case 'Consumer Secret (hidden): ' :
return 'my-consumer-secret' ;
}
return '' ;
});
$this -> io
-> method ( 'hasAuthentication' )
-> with ( $this -> equalTo ( 'bitbucket.org' ))
-> willReturnCallback ( function ( $repositoryName ) use ( & $initial_config ) {
return isset ( $initial_config [ $repositoryName ]);
});
$this -> io
-> method ( 'setAuthentication' )
-> willReturnCallback ( function ( string $repositoryName , string $username , ? string $password = null ) use ( & $initial_config ) {
$initial_config [ $repositoryName ] = [ 'username' => $username , 'password' => $password ];
});
$this -> io
-> method ( 'getAuthentication' )
-> willReturnCallback ( function ( string $repositoryName ) use ( & $initial_config ) {
if ( isset ( $initial_config [ $repositoryName ])) {
return $initial_config [ $repositoryName ];
}
return [ 'username' => null , 'password' => null ];
});
$downloader_mock = $this -> getHttpDownloaderMock ();
$downloader_mock -> expects ([
[ 'url' => 'https://bitbucket.org/site/oauth2/access_token' , 'status' => 200 , 'body' => '{"expires_in": 600, "access_token": "my-access-token"}' ]
]);
$this -> git -> setHttpDownloader ( $downloader_mock );
2024-11-06 12:49:06 +00:00
// @phpstan-ignore method.deprecated
2024-10-02 12:36:30 +00:00
$this -> git -> runCommand ( $commandCallable , $gitUrl , null , true );
}
public static function privateBitbucketWithOauthProvider () : array
{
return [
[ 'git@bitbucket.org:acme/repo.git' , 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git' ],
[ 'https://bitbucket.org/acme/repo.git' , 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git' ],
[ 'https://bitbucket.org/acme/repo' , 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git' ],
[ 'git@bitbucket.org:acme/repo.git' , 'https://x-token-auth:my-access-token@bitbucket.org/acme/repo.git' , [ 'bitbucket.org' => [ 'username' => 'someuseralsoswappedfortoken' , 'password' => 'little green men' ]]],
];
}
public static function privateBitbucketWithCredentialsProvider () : array
{
return [
[ 'git@bitbucket.org:acme/repo.git' , 'MY_BITBUCKET_TOKEN' , 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git' , 1 ],
[ 'https://bitbucket.org/acme/repo' , 'MY_BITBUCKET_TOKEN' , 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git' , 1 ],
[ 'https://bitbucket.org/acme/repo.git' , 'MY_BITBUCKET_TOKEN' , 'https://token:MY_BITBUCKET_TOKEN@bitbucket.org/acme/repo.git' , 1 ],
[ 'git@bitbucket.org:acme/repo.git' , null , 'git@bitbucket.org:acme/repo.git' , 0 ],
[ 'https://bitbucket.org/acme/repo' , null , 'git@bitbucket.org:acme/repo.git' , 1 , 1 ],
[ 'https://bitbucket.org/acme/repo.git' , null , 'git@bitbucket.org:acme/repo.git' , 1 , 1 ],
];
}
2022-11-24 13:39:08 +00:00
public static function privateGithubWithCredentialsProvider () : array
2019-11-06 23:43:19 +00:00
{
2022-08-17 12:20:07 +00:00
return [
[ 'git@github.com:acme/repo.git' , 'ssh' , 'MY_GITHUB_TOKEN' , 'https://token:MY_GITHUB_TOKEN@github.com/acme/repo.git' , 1 ],
[ 'https://github.com/acme/repo' , 'https' , 'MY_GITHUB_TOKEN' , 'https://token:MY_GITHUB_TOKEN@github.com/acme/repo.git' , 2 ],
];
2019-11-06 23:43:19 +00:00
}
2022-02-22 15:47:09 +00:00
private function mockConfig ( string $protocol ) : void
2019-11-06 23:43:19 +00:00
{
$this -> config
-> method ( 'get' )
2022-08-17 12:20:07 +00:00
-> willReturnMap ([
[ 'github-domains' , 0 , [ 'github.com' ]],
[ 'github-protocols' , 0 , [ $protocol ]],
]);
2019-11-06 23:43:19 +00:00
}
}