2017-07-08 01:06:38 +00:00
< ? php
2017-11-17 01:23:18 +00:00
/** @ license MIT
* Copyright 2017 J . King , Dustin Wilson et al .
* See LICENSE and AUTHORS files for details */
2017-07-08 01:06:38 +00:00
declare ( strict_types = 1 );
namespace JKingWeb\Arsse\Test ;
2017-08-29 14:50:31 +00:00
2021-02-27 20:24:02 +00:00
use Eloquent\Phony\Mock\Handle\InstanceHandle ;
use Eloquent\Phony\Phpunit\Phony ;
2020-01-24 20:54:08 +00:00
use GuzzleHttp\Exception\GuzzleException ;
use GuzzleHttp\Exception\RequestException ;
2017-07-08 01:06:38 +00:00
use JKingWeb\Arsse\Exception ;
2017-07-17 11:47:57 +00:00
use JKingWeb\Arsse\Arsse ;
2018-01-09 17:31:40 +00:00
use JKingWeb\Arsse\Conf ;
2019-06-21 22:52:27 +00:00
use JKingWeb\Arsse\Db\Driver ;
use JKingWeb\Arsse\Db\Result ;
2021-02-07 04:51:23 +00:00
use JKingWeb\Arsse\Factory ;
2017-07-17 11:47:57 +00:00
use JKingWeb\Arsse\Misc\Date ;
2019-09-25 22:30:53 +00:00
use JKingWeb\Arsse\Misc\URL ;
2022-08-06 02:08:36 +00:00
use JKingWeb\Arsse\Misc\HTTP ;
2018-01-11 16:09:25 +00:00
use Psr\Http\Message\MessageInterface ;
use Psr\Http\Message\RequestInterface ;
use Psr\Http\Message\ServerRequestInterface ;
2018-01-04 04:13:08 +00:00
use Psr\Http\Message\ResponseInterface ;
2022-08-06 20:03:50 +00:00
use GuzzleHttp\Psr7\ServerRequest ;
2017-07-08 01:06:38 +00:00
2017-07-20 22:36:03 +00:00
/** @coversNothing */
2017-07-08 01:06:38 +00:00
abstract class AbstractTest extends \PHPUnit\Framework\TestCase {
2019-10-16 18:42:43 +00:00
use \DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts ;
2022-06-04 02:10:49 +00:00
protected const COL_DEFS = [
'arsse_meta' => [
'key' => " str " ,
'value' => " str " ,
],
'arsse_users' => [
'id' => " str " ,
'password' => " str " ,
'num' => " int " ,
'admin' => " bool " ,
],
'arsse_user_meta' => [
'owner' => " str " ,
'key' => " str " ,
'modified' => " datetime " ,
'value' => " str " ,
],
'arsse_sessions' => [
'id' => " str " ,
'created' => " datetime " ,
'expires' => " datetime " ,
'user' => " str " ,
],
'arsse_tokens' => [
'id' => " str " ,
'class' => " str " ,
'user' => " str " ,
'created' => " datetime " ,
'expires' => " datetime " ,
'data' => " str " ,
],
'arsse_icons' => [
'id' => " int " ,
'url' => " str " ,
'modified' => " datetime " ,
'etag' => " str " ,
'next_fetch' => " datetime " ,
'orphaned' => " datetime " ,
'type' => " str " ,
'data' => " blob " ,
],
'arsse_articles' => [
'id' => " int " ,
2022-09-27 20:32:46 +00:00
'subscription' => " int " ,
'read' => " bool " ,
'starred' => " bool " ,
'hidden' => " bool " ,
2022-06-04 02:10:49 +00:00
'url' => " str " ,
'title' => " str " ,
'author' => " str " ,
'published' => " datetime " ,
'edited' => " datetime " ,
'modified' => " datetime " ,
2022-09-27 20:32:46 +00:00
'marked' => " datetime " ,
2022-06-04 02:10:49 +00:00
'guid' => " str " ,
'url_title_hash' => " str " ,
'url_content_hash' => " str " ,
'title_content_hash' => " str " ,
2022-09-27 20:32:46 +00:00
'note' => " str " ,
],
'arsse_article_contents' => [
'id' => " int " ,
'content' => " str " ,
2022-06-04 02:10:49 +00:00
],
'arsse_editions' => [
'id' => " int " ,
'article' => " int " ,
'modified' => " datetime " ,
],
'arsse_enclosures' => [
'article' => " int " ,
'url' => " str " ,
'type' => " str " ,
],
'arsse_categories' => [
'article' => " int " ,
'name' => " str " ,
],
'arsse_subscriptions' => [
'id' => " int " ,
'owner' => " str " ,
2022-09-19 17:06:19 +00:00
'url' => " str " ,
'feed_title' => " str " ,
'title' => " str " ,
'folder' => " int " ,
'last_mod' => " datetime " ,
'etag' => " str " ,
'next_fetch' => " datetime " ,
2022-06-04 02:10:49 +00:00
'added' => " datetime " ,
2022-09-19 17:06:19 +00:00
'source' => " str " ,
'updated' => " datetime " ,
'err_count' => " int " ,
'err_msg' => " str " ,
'size' => " int " ,
'icon' => " int " ,
2022-06-04 02:10:49 +00:00
'modified' => " datetime " ,
'order_type' => " int " ,
'pinned' => " bool " ,
2022-09-19 17:06:19 +00:00
'scrape' => " bool " ,
2022-06-04 02:10:49 +00:00
'keep_rule' => " str " ,
'block_rule' => " str " ,
2022-09-30 16:32:28 +00:00
'deleted' => " bool " ,
2022-06-04 02:10:49 +00:00
],
'arsse_folders' => [
'id' => " int " ,
'owner' => " str " ,
'parent' => " int " ,
'name' => " str " ,
'modified' => " datetime " ,
],
'arsse_tags' => [
'id' => " int " ,
'owner' => " str " ,
'name' => " str " ,
'modified' => " datetime " ,
],
'arsse_tag_members' => [
'tag' => " int " ,
'subscription' => " int " ,
'assigned' => " bool " ,
'modified' => " datetime " ,
],
'arsse_labels' => [
'id' => " int " ,
'owner' => " str " ,
'name' => " str " ,
'modified' => " datetime " ,
],
'arsse_label_members' => [
'label' => " int " ,
'article' => " int " ,
'assigned' => " bool " ,
'modified' => " datetime " ,
],
];
2021-02-27 20:24:02 +00:00
protected $objMock ;
protected $confMock ;
protected $langMock ;
protected $dbMock ;
protected $userMock ;
2018-01-06 17:02:45 +00:00
2021-02-27 20:24:02 +00:00
public function setUp () : void {
2018-11-23 15:01:17 +00:00
self :: clearData ();
2021-02-27 20:24:02 +00:00
// create the object factory as a mock
$this -> objMock = Arsse :: $obj = $this -> mock ( Factory :: class );
$this -> objMock -> get -> does ( function ( string $class ) {
return new $class ;
});
2018-01-06 17:02:45 +00:00
}
2020-01-20 18:34:03 +00:00
public static function clearData ( bool $loadLang = true ) : void {
2018-11-06 17:32:28 +00:00
date_default_timezone_set ( " America/Toronto " );
$r = new \ReflectionClass ( \JKingWeb\Arsse\Arsse :: class );
$props = array_keys ( $r -> getStaticProperties ());
foreach ( $props as $prop ) {
Arsse :: $$prop = null ;
}
if ( $loadLang ) {
2022-06-01 03:55:04 +00:00
Arsse :: $lang = new \JKingWeb\Arsse\Lang ;
2018-11-06 17:32:28 +00:00
}
}
2020-01-20 18:34:03 +00:00
public static function setConf ( array $conf = [], bool $force = true ) : void {
2018-11-17 02:32:27 +00:00
$defaults = [
2019-06-22 14:29:26 +00:00
'dbSQLite3File' => " :memory: " ,
'dbSQLite3Timeout' => 0 ,
2022-06-01 03:55:04 +00:00
'dbPostgreSQLHost' => $_ENV [ 'ARSSE_TEST_PGSQL_HOST' ] ? : " " ,
'dbPostgreSQLPort' => $_ENV [ 'ARSSE_TEST_PGSQL_PORT' ] ? : 5432 ,
'dbPostgreSQLUser' => $_ENV [ 'ARSSE_TEST_PGSQL_USER' ] ? : " arsse_test " ,
'dbPostgreSQLPass' => $_ENV [ 'ARSSE_TEST_PGSQL_PASS' ] ? : " arsse_test " ,
'dbPostgreSQLDb' => $_ENV [ 'ARSSE_TEST_PGSQL_DB' ] ? : " arsse_test " ,
2019-06-22 14:29:26 +00:00
'dbPostgreSQLSchema' => $_ENV [ 'ARSSE_TEST_PGSQL_SCHEMA' ] ? : " arsse_test " ,
2022-06-01 03:55:04 +00:00
'dbMySQLHost' => $_ENV [ 'ARSSE_TEST_MYSQL_HOST' ] ? : " localhost " ,
'dbMySQLPort' => $_ENV [ 'ARSSE_TEST_MYSQL_PORT' ] ? : 3306 ,
'dbMySQLUser' => $_ENV [ 'ARSSE_TEST_MYSQL_USER' ] ? : " arsse_test " ,
'dbMySQLPass' => $_ENV [ 'ARSSE_TEST_MYSQL_PASS' ] ? : " arsse_test " ,
'dbMySQLDb' => $_ENV [ 'ARSSE_TEST_MYSQL_DB' ] ? : " arsse_test " ,
2018-11-17 02:32:27 +00:00
];
2019-01-21 03:40:49 +00:00
Arsse :: $conf = (( $force ? null : Arsse :: $conf ) ? ? ( new Conf )) -> import ( $defaults ) -> import ( $conf );
2018-01-09 17:31:40 +00:00
}
2019-09-25 22:30:53 +00:00
protected function serverRequest ( string $method , string $url , string $urlPrefix , array $headers = [], array $vars = [], $body = null , string $type = " " , $params = [], string $user = null ) : ServerRequestInterface {
$server = [
'REQUEST_METHOD' => $method ,
2020-03-01 20:16:50 +00:00
'REQUEST_URI' => $url ,
2019-09-25 22:30:53 +00:00
];
if ( strlen ( $type )) {
$server [ 'HTTP_CONTENT_TYPE' ] = $type ;
}
if ( isset ( $params )) {
if ( is_array ( $params )) {
$params = implode ( " & " , array_map ( function ( $v , $k ) {
2019-10-25 19:16:35 +00:00
return rawurlencode ( $k ) . ( isset ( $v ) ? " = " . rawurlencode ( $v ) : " " );
2019-09-25 22:30:53 +00:00
}, $params , array_keys ( $params )));
}
$url = URL :: queryAppend ( $url , ( string ) $params );
2021-02-10 16:24:01 +00:00
$params = null ;
2019-09-25 22:30:53 +00:00
}
$q = parse_url ( $url , \PHP_URL_QUERY );
if ( strlen ( $q ? ? " " )) {
parse_str ( $q , $params );
} else {
$params = [];
}
$parsedBody = null ;
if ( isset ( $body )) {
if ( is_string ( $body ) && in_array ( strtolower ( $type ), [ " " , " application/x-www-form-urlencoded " ])) {
parse_str ( $body , $parsedBody );
} elseif ( ! is_string ( $body ) && in_array ( strtolower ( $type ), [ " application/json " , " text/json " ])) {
2019-10-25 19:16:35 +00:00
$body = json_encode ( $body , \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE );
2019-09-25 22:30:53 +00:00
} elseif ( ! is_string ( $body ) && in_array ( strtolower ( $type ), [ " " , " application/x-www-form-urlencoded " ])) {
$parsedBody = $body ;
$body = http_build_query ( $body , " a " , " & " );
}
}
$server = array_merge ( $server , $vars );
2022-08-06 20:03:50 +00:00
$req = new ServerRequest ( $method , $url , $headers , $body , " 1.1 " , $server );
2022-08-06 20:16:18 +00:00
$req = $req -> withParsedBody ( $parsedBody ) -> withQueryParams ( $params );
2019-09-25 22:30:53 +00:00
if ( isset ( $user )) {
if ( strlen ( $user )) {
$req = $req -> withAttribute ( " authenticated " , true ) -> withAttribute ( " authenticatedUser " , $user );
} else {
$req = $req -> withAttribute ( " authenticationFailed " , true );
}
}
2020-03-01 20:16:50 +00:00
if ( strlen ( $type ) && strlen ( $body ? ? " " )) {
2019-09-25 22:30:53 +00:00
$req = $req -> withHeader ( " Content-Type " , $type );
}
foreach ( $headers as $key => $value ) {
if ( ! is_null ( $value )) {
$req = $req -> withHeader ( $key , $value );
} else {
$req = $req -> withoutHeader ( $key );
}
}
$target = substr ( URL :: normalize ( $url ), strlen ( $urlPrefix ));
$req = $req -> withRequestTarget ( $target );
if ( strlen ( $body ? ? " " )) {
$p = $req -> getBody ();
$p -> write ( $body );
$req = $req -> withBody ( $p );
}
return $req ;
}
2021-03-01 23:20:50 +00:00
public static function assertMatchesRegularExpression ( string $pattern , string $string , string $message = '' ) : void {
if ( method_exists ( parent :: class , " assertMatchesRegularExpression " )) {
parent :: assertMatchesRegularExpression ( $pattern , $string , $message );
} else {
parent :: assertRegExp ( $pattern , $string , $message );
}
}
2021-06-25 15:08:56 +00:00
public static function assertFileDoesNotExist ( string $filename , string $message = '' ) : void {
if ( method_exists ( parent :: class , " assertFileDoesNotExist " )) {
parent :: assertFileDoesNotExist ( $filename , $message );
} else {
parent :: assertFileNotExists ( $filename , $message );
}
}
2020-01-20 18:34:03 +00:00
public function assertException ( $msg = " " , string $prefix = " " , string $type = " Exception " ) : void {
2017-08-29 14:50:31 +00:00
if ( func_num_args ()) {
2019-03-21 02:24:35 +00:00
if ( $msg instanceof \JKingWeb\Arsse\AbstractException ) {
$this -> expectException ( get_class ( $msg ));
$this -> expectExceptionCode ( $msg -> getCode ());
2017-07-08 01:06:38 +00:00
} else {
2020-03-01 20:16:50 +00:00
$class = \JKingWeb\Arsse\NS_BASE . ( $prefix !== " " ? str_replace ( " / " , " \\ " , $prefix ) . " \\ " : " " ) . $type ;
$msgID = ( $prefix !== " " ? $prefix . " / " : " " ) . $type . " . $msg " ;
2019-03-21 02:24:35 +00:00
if ( array_key_exists ( $msgID , Exception :: CODES )) {
$code = Exception :: CODES [ $msgID ];
} else {
$code = 0 ;
}
$this -> expectException ( $class );
$this -> expectExceptionCode ( $code );
2017-07-08 01:06:38 +00:00
}
} else {
// expecting a standard PHP exception
2019-01-21 03:40:49 +00:00
$this -> expectException ( \Throwable :: class );
2017-07-08 01:06:38 +00:00
}
}
2020-01-20 18:34:03 +00:00
protected function assertMessage ( MessageInterface $exp , MessageInterface $act , string $text = '' ) : void {
2018-01-11 16:09:25 +00:00
if ( $exp instanceof ResponseInterface ) {
$this -> assertInstanceOf ( ResponseInterface :: class , $act , $text );
2020-12-14 03:10:34 +00:00
$this -> assertSame ( $exp -> getStatusCode (), $act -> getStatusCode (), $text );
2018-01-11 16:09:25 +00:00
} elseif ( $exp instanceof RequestInterface ) {
if ( $exp instanceof ServerRequestInterface ) {
$this -> assertInstanceOf ( ServerRequestInterface :: class , $act , $text );
$this -> assertEquals ( $exp -> getAttributes (), $act -> getAttributes (), $text );
}
$this -> assertInstanceOf ( RequestInterface :: class , $act , $text );
2018-01-11 20:48:29 +00:00
$this -> assertSame ( $exp -> getMethod (), $act -> getMethod (), $text );
2018-01-11 16:09:25 +00:00
$this -> assertSame ( $exp -> getRequestTarget (), $act -> getRequestTarget (), $text );
}
2022-08-06 02:08:36 +00:00
if ( $exp instanceof ResponseInterface && HTTP :: matchType ( $exp , " application/json " , " text/json " , " +json " )) {
2022-08-06 20:03:50 +00:00
$expBody = @ json_decode (( string ) $exp -> getBody (), true );
$actBody = @ json_decode (( string ) $act -> getBody (), true );
2022-08-06 02:08:36 +00:00
$this -> assertSame ( \JSON_ERROR_NONE , json_last_error (), " Response body is not valid JSON " );
$this -> assertEquals ( $expBody , $actBody , $text );
$this -> assertSame ( $expBody , $actBody , $text );
} elseif ( $exp instanceof ResponseInterface && HTTP :: matchType ( $exp , " application/xml " , " text/xml " , " +xml " )) {
2019-07-24 13:10:13 +00:00
$this -> assertXmlStringEqualsXmlString (( string ) $exp -> getBody (), ( string ) $act -> getBody (), $text );
2018-01-06 17:02:45 +00:00
} else {
2020-12-14 03:10:34 +00:00
$this -> assertSame (( string ) $exp -> getBody (), ( string ) $act -> getBody (), $text );
2018-01-04 04:13:08 +00:00
}
2018-01-05 04:08:53 +00:00
$this -> assertEquals ( $exp -> getHeaders (), $act -> getHeaders (), $text );
2018-01-04 04:13:08 +00:00
}
2022-08-06 20:03:50 +00:00
protected function extractMessageJson ( MessageInterface $msg ) {
if ( HTTP :: matchType ( $msg , " application/json " , " text/json " , " +json " )) {
$json = @ json_decode (( string ) $msg -> getBody (), true );
if ( json_last_error () === \JSON_ERROR_NONE ) {
return $json ;
}
}
return null ;
}
2020-01-20 18:34:03 +00:00
public function assertTime ( $exp , $test , string $msg = '' ) : void {
2018-11-06 17:32:28 +00:00
$test = $this -> approximateTime ( $exp , $test );
2020-03-01 20:16:50 +00:00
$exp = Date :: transform ( $exp , " iso8601 " );
2018-11-06 17:32:28 +00:00
$test = Date :: transform ( $test , " iso8601 " );
$this -> assertSame ( $exp , $test , $msg );
}
2017-12-08 21:00:23 +00:00
public function approximateTime ( $exp , $act ) {
if ( is_null ( $act )) {
return null ;
2017-12-20 00:08:08 +00:00
} elseif ( is_null ( $exp )) {
return $act ;
2017-12-08 21:00:23 +00:00
}
$target = Date :: normalize ( $exp ) -> getTimeStamp ();
$value = Date :: normalize ( $act ) -> getTimeStamp ();
if ( $value >= ( $target - 1 ) && $value <= ( $target + 1 )) {
// if the actual time is off by no more than one second, it's acceptable
return $exp ;
} else {
return $act ;
}
}
2018-11-08 19:50:58 +00:00
public function stringify ( $value ) {
if ( ! is_array ( $value )) {
return $value ;
}
foreach ( $value as $k => $v ) {
if ( is_array ( $v )) {
2021-03-02 04:27:58 +00:00
$value [ $k ] = $this -> stringify ( $v );
2018-11-08 19:50:58 +00:00
} elseif ( is_int ( $v ) || is_float ( $v )) {
$value [ $k ] = ( string ) $v ;
}
}
return $value ;
}
2019-06-21 22:52:27 +00:00
2022-06-05 21:41:32 +00:00
/** Inserts into the database test data in the following format :
2022-09-15 14:12:04 +00:00
*
2022-06-05 21:41:32 +00:00
* `` ` php
* $data = [
* 'some_table' => [
* 'columns' => [ " id " , " name " ],
* 'rows' => [
* [ 1 , " Dupond " ],
* [ 2 , " Dupont " ],
* ]
* ],
* 'other_table' => [
* ...
* ]
* ];
* `` `
*/
2019-06-21 22:52:27 +00:00
public function primeDatabase ( Driver $drv , array $data ) : bool {
$tr = $drv -> begin ();
foreach ( $data as $table => $info ) {
$cols = array_map ( function ( $v ) {
return '"' . str_replace ( '"' , '""' , $v ) . '"' ;
2022-06-05 21:41:32 +00:00
}, $info [ 'columns' ]);
2019-06-21 22:52:27 +00:00
$cols = implode ( " , " , $cols );
2022-06-05 21:41:32 +00:00
$bindings = array_map ( function ( $c ) use ( $table ) {
return self :: COL_DEFS [ $table ][ $c ];
}, $info [ 'columns' ]);
2019-06-21 22:52:27 +00:00
$params = implode ( " , " , array_fill ( 0 , sizeof ( $info [ 'columns' ]), " ? " ));
$s = $drv -> prepareArray ( " INSERT INTO $table ( $cols ) values( $params ) " , $bindings );
foreach ( $info [ 'rows' ] as $row ) {
$s -> runArray ( $row );
}
}
$tr -> commit ();
$this -> primed = true ;
return true ;
}
2022-06-04 02:10:49 +00:00
public function compareExpectations ( Driver $drv , array $expected ) : void {
2019-06-21 22:52:27 +00:00
foreach ( $expected as $table => $info ) {
2022-06-04 02:10:49 +00:00
// serialize the rows of the expected output
$exp = [];
$dates = [];
foreach ( $info [ 'rows' ] as $r ) {
$row = [];
foreach ( $r as $c => $v ) {
2022-06-05 00:16:22 +00:00
// store any date values for later comparison
2022-06-06 14:51:41 +00:00
if ( is_string ( $v ) && preg_match ( " /^ \ d { 4}- \ d \ d- \ d \ d \ d \ d: \ d \ d: \ d \ d $ / " , $v )) {
2022-06-04 02:10:49 +00:00
$dates [] = $v ;
}
2022-06-05 00:16:22 +00:00
// serialize to CSV, null being represented by no value
2022-06-04 02:10:49 +00:00
if ( $v === null ) {
$row [] = " " ;
2022-06-05 03:02:24 +00:00
} elseif ( $drv -> stringOutput () || is_string ( $v )) {
2022-06-04 02:10:49 +00:00
$row [] = '"' . str_replace ( '"' , '""' , ( string ) $v ) . '"' ;
} else {
$row [] = ( string ) $v ;
}
}
$exp [] = implode ( " , " , $row );
}
// serialize the rows of the actual output
$cols = implode ( " , " , array_map ( function ( $v ) {
2019-06-21 22:52:27 +00:00
return '"' . str_replace ( '"' , '""' , $v ) . '"' ;
2022-06-05 21:41:32 +00:00
}, $info [ 'columns' ]));
2019-06-21 22:52:27 +00:00
$data = $drv -> prepare ( " SELECT $cols from $table " ) -> run () -> getAll ();
2022-06-04 02:10:49 +00:00
$act = [];
2022-06-05 00:16:22 +00:00
$extra = [];
2022-06-04 02:10:49 +00:00
foreach ( $data as $r ) {
$row = [];
foreach ( $r as $c => $v ) {
2022-06-05 00:16:22 +00:00
// account for dates which might be off by one second
2022-06-06 14:51:41 +00:00
if ( is_string ( $v ) && preg_match ( " /^ \ d { 4}- \ d \ d- \ d \ d \ d \ d: \ d \ d: \ d \ d $ / " , $v )) {
2022-06-04 02:10:49 +00:00
if ( array_search ( $v , $dates , true ) === false ) {
$v = Date :: transform ( Date :: sub ( " PT1S " , $v ), " sql " );
if ( array_search ( $v , $dates , true ) === false ) {
$v = Date :: transform ( Date :: add ( " PT2S " , $v ), " sql " );
if ( array_search ( $v , $dates , true ) === false ) {
$v = Date :: transform ( Date :: sub ( " PT1S " , $v ), " sql " );
}
}
2019-06-21 22:52:27 +00:00
}
}
2022-06-04 02:10:49 +00:00
if ( $v === null ) {
$row [] = " " ;
} elseif ( is_string ( $v )) {
$row [] = '"' . str_replace ( '"' , '""' , ( string ) $v ) . '"' ;
} else {
$row [] = ( string ) $v ;
2019-06-21 22:52:27 +00:00
}
}
2022-06-05 00:16:22 +00:00
$row = implode ( " , " , $row );
// now search for the actual output row in the expected output
$found = array_keys ( $exp , $row , true );
foreach ( $found as $k ) {
2022-09-15 14:12:04 +00:00
if ( ! isset ( $act [ $k ])) {
2022-06-05 00:16:22 +00:00
$act [ $k ] = $row ;
// skip to the next row
continue 2 ;
}
}
// if the row was not found, add it to a buffer which will be added to the actual output once all found rows are processed
$extra [] = $row ;
}
// add any unfound rows to the end of the actual array
2022-06-06 14:51:41 +00:00
$base = sizeof ( $exp );
2022-06-05 00:16:22 +00:00
foreach ( $extra as $k => $v ) {
$act [ $base + $k ] = $v ;
2019-06-21 22:52:27 +00:00
}
2022-06-05 00:16:22 +00:00
// sort the actual output by keys
ksort ( $act );
// finally perform the comparison to be shown to the tester
2022-06-04 02:10:49 +00:00
$this -> assertSame ( $exp , $act , " Actual table $table does not match expectations " );
2019-06-21 22:52:27 +00:00
}
}
2019-07-05 23:01:34 +00:00
public function primeExpectations ( array $source , array $tableSpecs ) : array {
2019-06-21 22:52:27 +00:00
$out = [];
foreach ( $tableSpecs as $table => $columns ) {
// make sure the source has the table we want
2022-06-05 21:41:32 +00:00
if ( ! isset ( $source [ $table ])) {
2022-10-07 22:38:17 +00:00
throw new \Exception ( " Source for expectations does not contain requested table $table . " );
2022-06-05 21:41:32 +00:00
}
// fill the output, particularly the correct number of (empty) rows
$rows = sizeof ( $source [ $table ][ 'rows' ]);
2019-06-21 22:52:27 +00:00
$out [ $table ] = [
2022-06-05 21:41:32 +00:00
'columns' => $columns ,
'rows' => array_fill ( 0 , $rows , []),
2019-06-21 22:52:27 +00:00
];
2022-06-05 21:41:32 +00:00
// fill the rows with the requested data, column-wise
foreach ( $columns as $c ) {
if (( $index = array_search ( $c , $source [ $table ][ 'columns' ], true )) === false ) {
2022-10-07 22:38:17 +00:00
throw new \Exception ( " Expected column $table . $c is not present in test data " );
2022-06-05 21:41:32 +00:00
}
for ( $a = 0 ; $a < $rows ; $a ++ ) {
$out [ $table ][ 'rows' ][ $a ][] = $source [ $table ][ 'rows' ][ $a ][ $index ];
2019-06-21 22:52:27 +00:00
}
}
}
return $out ;
}
2020-01-20 18:34:03 +00:00
public function assertResult ( array $expected , Result $data ) : void {
2021-03-02 16:04:21 +00:00
$data = $data -> getAll ();
2021-03-02 04:27:58 +00:00
// stringify our expectations if necessary
if ( static :: $stringOutput ? ? false ) {
$expected = $this -> stringify ( $expected );
}
2019-06-21 22:52:27 +00:00
if ( sizeof ( $expected )) {
// make sure the expectations are consistent
foreach ( $expected as $exp ) {
if ( ! isset ( $keys )) {
$keys = $exp ;
continue ;
}
$this -> assertSame ( array_keys ( $keys ), array_keys ( $exp ), " Result set expectations are irregular " );
}
// filter the result set to contain just the desired keys (we don't care if the result has extra keys)
$rows = [];
2021-03-02 04:27:58 +00:00
$keys = array_keys ( $keys );
2019-06-21 22:52:27 +00:00
foreach ( $data as $row ) {
2021-03-02 04:27:58 +00:00
$r = [];
foreach ( $keys as $k ) {
if ( array_key_exists ( $k , $row )) {
$r [ $k ] = $row [ $k ];
}
}
$rows [] = $r ;
2019-06-21 22:52:27 +00:00
}
// compare the result set to the expectations
foreach ( $rows as $row ) {
2021-03-02 04:27:58 +00:00
$this -> assertContains ( $row , $expected , " Result set contains unexpected record. \n " . var_export ( $expected , true ));
2019-06-21 22:52:27 +00:00
$found = array_search ( $row , $expected );
unset ( $expected [ $found ]);
}
$this -> assertArraySubset ( $expected , [], false , " Expectations not in result set. " );
}
}
2020-01-24 20:54:08 +00:00
/** Guzzle's exception classes require some fairly complicated construction; this abstracts it all away so that only message and code need be supplied */
protected function mockGuzzleException ( string $class , ? string $message = null , ? int $code = null , ? \Throwable $e = null ) : GuzzleException {
if ( is_a ( $class , RequestException :: class , true )) {
2021-02-27 20:24:02 +00:00
$req = $this -> mock ( RequestInterface :: class );
$res = $this -> mock ( ResponseInterface :: class );
$res -> getStatusCode -> returns ( $code ? ? 0 );
return new $class ( $message ? ? " " , $req -> get (), $res -> get (), $e );
2020-01-24 20:54:08 +00:00
} else {
return new $class ( $message ? ? " " , $code ? ? 0 , $e );
}
}
2021-02-27 20:24:02 +00:00
protected function mock ( string $class ) : InstanceHandle {
return Phony :: mock ( $class );
}
protected function partialMock ( string $class , ... $argument ) : InstanceHandle {
return Phony :: partialMock ( $class , $argument );
}
2017-08-29 14:50:31 +00:00
}