2021-06-26 11:23:15 -04:00
< ? php
namespace Tests ;
2015-07-12 15:01:42 -04:00
2020-11-21 19:17:45 -05:00
use BookStack\Entities\Models\Entity ;
2022-07-23 10:10:18 -04:00
use BookStack\Settings\SettingService ;
use BookStack\Uploads\HttpFetcher ;
use GuzzleHttp\Client ;
use GuzzleHttp\Handler\MockHandler ;
use GuzzleHttp\HandlerStack ;
use GuzzleHttp\Middleware ;
2022-09-26 20:27:51 -04:00
use Illuminate\Contracts\Console\Kernel ;
2017-02-05 09:37:50 -05:00
use Illuminate\Foundation\Testing\DatabaseTransactions ;
2017-02-04 06:58:42 -05:00
use Illuminate\Foundation\Testing\TestCase as BaseTestCase ;
2022-07-23 10:10:18 -04:00
use Illuminate\Http\JsonResponse ;
use Illuminate\Support\Env ;
2022-09-26 16:25:32 -04:00
use Illuminate\Support\Facades\DB ;
2022-07-23 10:10:18 -04:00
use Illuminate\Support\Facades\Log ;
use Illuminate\Testing\Assert as PHPUnit ;
2023-01-21 06:08:34 -05:00
use Mockery ;
2022-07-23 10:10:18 -04:00
use Monolog\Handler\TestHandler ;
use Monolog\Logger ;
use Psr\Http\Client\ClientInterface ;
use Ssddanbrown\AssertHtml\TestsHtml ;
2022-09-29 11:49:25 -04:00
use Tests\Helpers\EntityProvider ;
2023-02-08 09:39:13 -05:00
use Tests\Helpers\FileProvider ;
2023-01-21 06:08:34 -05:00
use Tests\Helpers\PermissionsProvider ;
2022-09-29 17:11:16 -04:00
use Tests\Helpers\TestServiceProvider ;
2023-01-21 06:08:34 -05:00
use Tests\Helpers\UserRoleProvider ;
2015-09-02 13:26:33 -04:00
2017-02-04 06:58:42 -05:00
abstract class TestCase extends BaseTestCase
2015-07-12 15:01:42 -04:00
{
2017-02-04 06:58:42 -05:00
use CreatesApplication ;
2017-02-05 09:37:50 -05:00
use DatabaseTransactions ;
2022-07-23 10:10:18 -04:00
use TestsHtml ;
2022-09-29 11:49:25 -04:00
protected EntityProvider $entities ;
2023-01-21 06:08:34 -05:00
protected UserRoleProvider $users ;
protected PermissionsProvider $permissions ;
2023-02-08 09:39:13 -05:00
protected FileProvider $files ;
2022-09-29 11:49:25 -04:00
protected function setUp () : void
{
$this -> entities = new EntityProvider ();
2023-01-21 06:08:34 -05:00
$this -> users = new UserRoleProvider ();
$this -> permissions = new PermissionsProvider ( $this -> users );
2023-02-08 09:39:13 -05:00
$this -> files = new FileProvider ();
2023-01-21 06:08:34 -05:00
2022-09-29 11:49:25 -04:00
parent :: setUp ();
2023-01-21 15:50:04 -05:00
// We can uncomment the below to run tests with failings upon deprecations.
// Can't leave on since some deprecations can only be fixed upstream.
// $this->withoutDeprecationHandling();
2022-09-29 11:49:25 -04:00
}
2018-09-21 10:15:16 -04:00
2018-01-28 08:33:50 -05:00
/**
* The base URL to use while testing the application .
*/
2022-07-23 10:10:18 -04:00
protected string $baseUrl = 'http://localhost' ;
2018-01-28 08:33:50 -05:00
2022-09-26 20:27:51 -04:00
/**
* Creates the application .
*
* @ return \Illuminate\Foundation\Application
*/
public function createApplication ()
{
/** @var \Illuminate\Foundation\Application $app */
$app = require __DIR__ . '/../bootstrap/app.php' ;
$app -> register ( TestServiceProvider :: class );
$app -> make ( Kernel :: class ) -> bootstrap ();
return $app ;
}
2018-09-21 10:15:16 -04:00
/**
2022-07-23 10:10:18 -04:00
* Set the current user context to be an admin .
2018-09-21 10:15:16 -04:00
*/
2022-07-23 10:10:18 -04:00
public function asAdmin ()
2018-09-21 10:15:16 -04:00
{
2023-01-21 06:08:34 -05:00
return $this -> actingAs ( $this -> users -> admin ());
2018-09-21 10:15:16 -04:00
}
/**
2022-07-23 10:10:18 -04:00
* Set the current user context to be an editor .
*/
public function asEditor ()
{
2023-01-21 06:08:34 -05:00
return $this -> actingAs ( $this -> users -> editor ());
2022-07-23 10:10:18 -04:00
}
2022-09-03 07:32:21 -04:00
/**
* Set the current user context to be a viewer .
*/
public function asViewer ()
{
2023-01-21 06:08:34 -05:00
return $this -> actingAs ( $this -> users -> viewer ());
2022-07-23 10:10:18 -04:00
}
/**
* Quickly sets an array of settings .
2018-09-21 10:15:16 -04:00
*/
2022-07-23 10:10:18 -04:00
protected function setSettings ( array $settingsArray ) : void
2018-09-21 10:15:16 -04:00
{
2022-07-23 10:10:18 -04:00
$settings = app ( SettingService :: class );
foreach ( $settingsArray as $key => $value ) {
$settings -> put ( $key , $value );
}
}
/**
* Mock the HttpFetcher service and return the given data on fetch .
*/
protected function mockHttpFetch ( $returnData , int $times = 1 )
{
$mockHttp = Mockery :: mock ( HttpFetcher :: class );
$this -> app [ HttpFetcher :: class ] = $mockHttp ;
$mockHttp -> shouldReceive ( 'fetch' )
-> times ( $times )
-> andReturn ( $returnData );
}
/**
* Mock the http client used in BookStack .
* Returns a reference to the container which holds all history of http transactions .
2021-06-26 11:23:15 -04:00
*
2022-07-23 10:10:18 -04:00
* @ link https :// docs . guzzlephp . org / en / stable / testing . html #history-middleware
2018-09-21 10:15:16 -04:00
*/
2022-07-23 10:10:18 -04:00
protected function & mockHttpClient ( array $responses = []) : array
2018-09-21 10:15:16 -04:00
{
2022-07-23 10:10:18 -04:00
$container = [];
$history = Middleware :: history ( $container );
$mock = new MockHandler ( $responses );
$handlerStack = new HandlerStack ( $mock );
$handlerStack -> push ( $history );
$this -> app [ ClientInterface :: class ] = new Client ([ 'handler' => $handlerStack ]);
return $container ;
}
/**
* Run a set test with the given env variable .
* Remembers the original and resets the value after test .
2022-09-26 16:25:32 -04:00
* Database config is juggled so the value can be restored when
* parallel testing are used , where multiple databases exist .
2022-07-23 10:10:18 -04:00
*/
protected function runWithEnv ( string $name , $value , callable $callback )
{
Env :: disablePutenv ();
$originalVal = $_SERVER [ $name ] ? ? null ;
if ( is_null ( $value )) {
unset ( $_SERVER [ $name ]);
} else {
$_SERVER [ $name ] = $value ;
}
2022-09-26 16:25:32 -04:00
$database = config ( 'database.connections.mysql_testing.database' );
2022-07-23 10:10:18 -04:00
$this -> refreshApplication ();
2022-09-26 16:25:32 -04:00
DB :: purge ();
config () -> set ( 'database.connections.mysql_testing.database' , $database );
2023-01-21 08:03:47 -05:00
DB :: beginTransaction ();
2022-09-26 16:25:32 -04:00
2022-07-23 10:10:18 -04:00
$callback ();
2023-01-21 08:03:47 -05:00
DB :: rollBack ();
2022-07-23 10:10:18 -04:00
if ( is_null ( $originalVal )) {
unset ( $_SERVER [ $name ]);
} else {
$_SERVER [ $name ] = $originalVal ;
}
}
/**
* Check the keys and properties in the given map to include
* exist , albeit not exclusively , within the map to check .
*/
protected function assertArrayMapIncludes ( array $mapToInclude , array $mapToCheck , string $message = '' ) : void
{
$passed = true ;
foreach ( $mapToInclude as $key => $value ) {
if ( ! isset ( $mapToCheck [ $key ]) || $mapToCheck [ $key ] !== $mapToInclude [ $key ]) {
$passed = false ;
}
}
$toIncludeStr = print_r ( $mapToInclude , true );
$toCheckStr = print_r ( $mapToCheck , true );
self :: assertThat ( $passed , self :: isTrue (), " Failed asserting that given map: \n \n { $toCheckStr } \n \n includes: \n \n { $toIncludeStr } " );
}
/**
* Assert a permission error has occurred .
*/
protected function assertPermissionError ( $response )
{
PHPUnit :: assertTrue ( $this -> isPermissionError ( $response -> baseResponse ? ? $response -> response ), 'Failed asserting the response contains a permission error.' );
}
/**
* Assert a permission error has occurred .
*/
protected function assertNotPermissionError ( $response )
{
PHPUnit :: assertFalse ( $this -> isPermissionError ( $response -> baseResponse ? ? $response -> response ), 'Failed asserting the response does not contain a permission error.' );
}
/**
* Check if the given response is a permission error .
*/
private function isPermissionError ( $response ) : bool
{
return $response -> status () === 302
&& (
(
$response -> headers -> get ( 'Location' ) === url ( '/' )
&& strpos ( session () -> pull ( 'error' , '' ), 'You do not have permission to access' ) === 0
)
||
(
$response instanceof JsonResponse &&
$response -> json ([ 'error' => 'You do not have permission to perform the requested action.' ])
)
);
}
/**
* Assert that the session has a particular error notification message set .
*/
protected function assertSessionError ( string $message )
{
$error = session () -> get ( 'error' );
PHPUnit :: assertTrue ( $error === $message , " Failed asserting the session contains an error. \n Found: { $error } \n Expecting: { $message } " );
}
/**
* Assert the session contains a specific entry .
*/
protected function assertSessionHas ( string $key ) : self
{
$this -> assertTrue ( session () -> has ( $key ), " Session does not contain a [ { $key } ] entry " );
return $this ;
}
protected function assertNotificationContains ( \Illuminate\Testing\TestResponse $resp , string $text )
{
2022-11-23 07:07:46 -05:00
return $this -> withHtml ( $resp ) -> assertElementContains ( '.notification[role="alert"]' , $text );
2022-07-23 10:10:18 -04:00
}
/**
* Set a test handler as the logging interface for the application .
* Allows capture of logs for checking against during tests .
*/
protected function withTestLogger () : TestHandler
{
$monolog = new Logger ( 'testing' );
$testHandler = new TestHandler ();
$monolog -> pushHandler ( $testHandler );
Log :: extend ( 'testing' , function () use ( $monolog ) {
return $monolog ;
});
Log :: setDefaultDriver ( 'testing' );
return $testHandler ;
2017-08-28 08:55:39 -04:00
}
2020-01-12 09:45:54 -05:00
/**
* Assert that an activity entry exists of the given key .
* Checks the activity belongs to the given entity if provided .
*/
2021-09-18 16:29:42 -04:00
protected function assertActivityExists ( string $type , ? Entity $entity = null , string $detail = '' )
2020-01-12 09:45:54 -05:00
{
2020-11-07 19:03:19 -05:00
$detailsToCheck = [ 'type' => $type ];
2020-01-12 09:45:54 -05:00
if ( $entity ) {
$detailsToCheck [ 'entity_type' ] = $entity -> getMorphClass ();
$detailsToCheck [ 'entity_id' ] = $entity -> id ;
}
2021-09-15 15:55:10 -04:00
if ( $detail ) {
$detailsToCheck [ 'detail' ] = $detail ;
}
2020-01-12 09:45:54 -05:00
$this -> assertDatabaseHas ( 'activities' , $detailsToCheck );
}
2021-06-26 11:23:15 -04:00
}