first commit

This commit is contained in:
2024-11-10 21:08:49 +01:00
commit 0d932ce5ee
14455 changed files with 2567501 additions and 0 deletions

View File

@@ -0,0 +1,438 @@
<?php
use WP_Mock\Tools\TestCase;
class WP_Async_Task_Tests extends TestCase {
/**
* Set up some test mocks
*/
public function setUp() {
parent::setUp();
$_COOKIE = array();
$_POST = array();
}
public function tearDown() {
parent::tearDown();
$_COOKIE = array();
$_POST = array();
}
/**
* Test that the correct actions are registered based on the auth level
*/
public function test_auth_level_both() {
$async = new Async( false );
WP_Mock::expectActionAdded( 'async', array( $async, 'launch' ), 10, 20 );
WP_Mock::expectActionAdded( 'admin_post_wp_async_async', array( $async, 'handle_postback' ) );
WP_Mock::expectActionAdded( 'admin_post_nopriv_wp_async_async', array( $async, 'handle_postback' ) );
$async->__construct( WP_Async_Task::BOTH );
$this->assertConditionsMet();
}
/**
* Test that the correct actions are registered based on the auth level
*/
public function test_auth_level_logged_in_only() {
$async = new Async( false );
WP_Mock::expectActionAdded( 'async', array( $async, 'launch' ), 10, 20 );
WP_Mock::expectActionAdded( 'admin_post_wp_async_async', array( $async, 'handle_postback' ) );
$async->__construct( WP_Async_Task::LOGGED_IN );
$this->assertConditionsMet();
}
/**
* Test that the correct actions are registered based on the auth level
*/
public function test_auth_level_logged_out_only() {
$async = new Async( false );
WP_Mock::expectActionAdded( 'async', array( $async, 'launch' ), 10, 20 );
WP_Mock::expectActionAdded( 'admin_post_nopriv_wp_async_async', array( $async, 'handle_postback' ) );
$async->__construct( WP_Async_Task::LOGGED_OUT );
$this->assertConditionsMet();
}
/**
* Test that the constructor throws an Exception if action is undefined
*
* @expectedException \Exception
*/
public function test_empty_action() {
new EmptyAsync();
}
/**
* Test that throwing an Exception in prepare_data stops a postback from firing
*/
public function test_exception_stops_launch_sequence() {
$async = $this->getMockAsync( 'Async', array( 'prepare_data', 'create_async_nonce' ) );
$arg1 = rand( 0, 9 );
$arg2 = rand( 10, 99 );
$async->shouldReceive( 'prepare_data' )
->once()
->with( array( $arg1, $arg2 ) )
->andThrow( 'Exception' );
$async->shouldReceive( 'create_async_nonce' )->never();
/** @var Async $async */
$async->launch( $arg1, $arg2 );
$this->assertConditionsMet();
}
/**
* Test that launch sets the correct action and _nonce values in the body
*/
public function test_launch() {
$async = $this->getMockAsync( 'Async', array( 'prepare_data', 'create_async_nonce' ) );
$arg = 'arg' . rand( 0, 9 );
$async->shouldReceive( 'prepare_data' )
->once()
->with( array( $arg ) )
->andReturn( array( 'foo' => $arg ) );
$nonce = substr( md5( 'async' . rand( 0, 9 ) ), - 12, 10 );
$async->shouldReceive( 'create_async_nonce' )
->once()
->with()
->andReturn( $nonce );
$body_data = new ReflectionProperty( 'Async', '_body_data' );
$body_data->setAccessible( true );
WP_Mock::wpFunction( 'has_action', array(
'times' => 1,
'args' => array( 'shutdown', array( $async, 'launch_on_shutdown' ) ),
'return' => false,
) );
WP_Mock::expectActionAdded( 'shutdown', array( $async, 'launch_on_shutdown' ) );
/** @var Async $async */
$async->launch( $arg );
$data = $body_data->getValue( $async );
$this->assertArrayHasKey( 'action', $data );
$this->assertEquals( 'wp_async_async', $data['action'] );
$this->assertArrayHasKey( '_nonce', $data );
$this->assertEquals( $nonce, $data['_nonce'] );
$this->assertConditionsMet();
}
public function test_launch_on_shutdown() {
$async = $this->getMockAsync( 'Async', array( 'prepare_data', 'create_async_nonce' ) );
$async->shouldReceive( 'prepare_data' )->andReturn( array() );
$async->shouldReceive( 'create_async_nonce' )->andReturn( 'asdf' );
WP_Mock::wpFunction( 'maybe_serialize', array(
'return' => function ( $thing ) {
return is_scalar( $thing ) ? $thing : serialize( $thing );
}
) );
$_COOKIE = array(
'_some_cookie' => 'Value',
'foo' => 'bar',
'random' => rand( 0, 999999 ),
'array' => array( 'not', 'scalar' ),
);
$cookie_header = '';
array_walk( $_COOKIE, function ( $value, $key ) use ( &$cookie_header ) {
if ( ! empty( $cookie_header ) ) {
$cookie_header .= '; ';
}
if ( ! is_scalar( $value ) ) {
$value = serialize( $value );
}
$cookie_header .= "$key=" . urlencode( $value );
} );
$verify_ssl = (bool) rand( 0, 1 );
WP_Mock::onFilter( 'https_local_ssl_verify' )->with( true )->reply( $verify_ssl );
WP_Mock::wpFunction( 'admin_url', array(
'times' => 1,
'args' => array( 'admin-post.php' ),
'return' => $url = 'https://tctechcrunch2011.wordpress.com/wp-admin/admin-post.php'
) );
WP_Mock::wpFunction( 'wp_remote_post', array(
'times' => 1,
'args' => array(
$url,
array(
'timeout' => 0.01,
'blocking' => false,
'sslverify' => $verify_ssl,
'body' => array(
'action' => 'wp_async_async',
'_nonce' => 'asdf',
),
'headers' => array(
'cookie' => $cookie_header,
),
)
),
) );
/** @var Async $async */
$async->launch(); // to set up body data, etc.
$async->launch_on_shutdown();
$this->assertConditionsMet();
}
public function test_launch_on_shutdown_empty_body() {
WP_Mock::wpFunction( 'wp_remote_post', array( 'times' => 0, ) );
/** @var Async $async */
$async = $this->getMockAsync();
$async->launch_on_shutdown();
$this->assertConditionsMet();
}
public function test_handle_postback_nonce_not_set() {
$async = $this->getMockAsync( 'Async', array( 'verify_async_nonce', 'run_action' ) );
$async->shouldReceive( 'verify_async_nonce' )->never();
$async->shouldReceive( 'run_action' )->never();
WP_Mock::expectFilterAdded( 'wp_die_handler', function () {
die();
} );
WP_Mock::wpFunction( 'wp_die', array( 'times' => 1 ) );
/** @var Async $async */
$async->handle_postback();
$this->assertConditionsMet();
}
public function test_handle_postback_invalid_nonce() {
$async = $this->getMockAsync( 'Async', array( 'verify_async_nonce', 'run_action' ) );
$nonce = 'asdfasdf';
$_POST['_nonce'] = $nonce;
$async->shouldReceive( 'verify_async_nonce' )
->once()
->with( $nonce )
->andReturn( false );
$async->shouldReceive( 'run_action' )->never();
WP_Mock::expectFilterAdded( 'wp_die_handler', function () {
die();
} );
WP_Mock::wpFunction( 'wp_die', array( 'times' => 1 ) );
/** @var Async $async */
$async->handle_postback();
$this->assertConditionsMet();
}
public function test_handle_postback_anon() {
$async = $this->getMockAsync( 'Async', array( 'verify_async_nonce', 'run_action' ) );
$nonce = 'asdfasdf';
$_POST['_nonce'] = $nonce;
$async->shouldReceive( 'verify_async_nonce' )
->once()
->with( $nonce )
->andReturn( true );
WP_Mock::wpFunction( 'is_user_logged_in', array( 'times' => 1, 'return' => false, ) );
$async->shouldReceive( 'run_action' )
->once()
->with();
WP_Mock::expectFilterAdded( 'wp_die_handler', function () {
die();
} );
WP_Mock::wpFunction( 'wp_die', array( 'times' => 1 ) );
/** @var Async $async */
$async->handle_postback();
$action = new ReflectionProperty( 'Async', 'action' );
$action->setAccessible( true );
$this->assertEquals( 'nopriv_async', $action->getValue( $async ) );
$this->assertConditionsMet();
}
public function test_handle_postback() {
$async = $this->getMockAsync( 'Async', array( 'verify_async_nonce', 'run_action' ) );
$nonce = 'asdfasdf';
$_POST['_nonce'] = $nonce;
$async->shouldReceive( 'verify_async_nonce' )
->once()
->with( $nonce )
->andReturn( true );
WP_Mock::wpFunction( 'is_user_logged_in', array( 'times' => 1, 'return' => true, ) );
$async->shouldReceive( 'run_action' )
->once()
->with();
WP_Mock::expectFilterAdded( 'wp_die_handler', function () {
die();
} );
WP_Mock::wpFunction( 'wp_die', array( 'times' => 1 ) );
/** @var Async $async */
$async->handle_postback();
$action = new ReflectionProperty( 'Async', 'action' );
$action->setAccessible( true );
$this->assertEquals( 'async', $action->getValue( $async ) );
$this->assertConditionsMet();
}
public function test_create_async_nonce() {
$async = $this->getMockAsync();
$nonce_tick = rand( 10, 99 );
WP_Mock::wpFunction( 'wp_nonce_tick', array(
'times' => 1,
'args' => array(),
'return' => $nonce_tick,
) );
$create_nonce = new ReflectionMethod( 'Async', 'create_async_nonce' );
$create_nonce->setAccessible( true );
$expected_hash = md5( $nonce_tick . 'wp_async_async' . get_class( $async ) );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 1,
'args' => array( $nonce_tick . 'wp_async_async' . get_class( $async ), 'nonce' ),
'return' => $expected_hash,
) );
$this->assertEquals( substr( $expected_hash, - 12, 10 ), $create_nonce->invoke( $async ) );
$this->assertConditionsMet();
}
public function test_verify_async_nonce_invalid() {
$async = $this->getMockAsync();
$nonce_tick = rand( 10, 99 );
WP_Mock::wpFunction( 'wp_nonce_tick', array(
'times' => 1,
'args' => array(),
'return' => $nonce_tick,
) );
$verify_nonce = new ReflectionMethod( 'Async', 'verify_async_nonce' );
$verify_nonce->setAccessible( true );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 2,
'return' => md5( rand( 100, 999 ) ),
) );
$this->assertFalse( $verify_nonce->invoke( $async, md5( $nonce_tick ) ) );
$this->assertConditionsMet();
}
public function test_verify_async_nonce_recent() {
$async = $this->getMockAsync();
$nonce_tick = rand( 10, 99 );
WP_Mock::wpFunction( 'wp_nonce_tick', array(
'times' => 1,
'args' => array(),
'return' => $nonce_tick,
) );
$verify_nonce = new ReflectionMethod( 'Async', 'verify_async_nonce' );
$verify_nonce->setAccessible( true );
$hash = md5( $nonce_tick . 'wp_async_async' . get_class( $async ) );
$nonce = substr( $hash, - 12, 10 );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 1,
'args' => array( $nonce_tick . 'wp_async_async' . get_class( $async ), 'nonce' ),
'return' => function ( $thing ) {
return md5( $thing );
}
) );
$this->assertSame( 1, $verify_nonce->invoke( $async, $nonce ) );
$this->assertConditionsMet();
}
public function test_verify_async_nonce_old_but_valid() {
$async = $this->getMockAsync();
$nonce_tick = rand( 10, 99 );
$real_tick = $nonce_tick - 1;
WP_Mock::wpFunction( 'wp_nonce_tick', array(
'times' => 1,
'args' => array(),
'return' => $nonce_tick,
) );
$verify_nonce = new ReflectionMethod( 'Async', 'verify_async_nonce' );
$verify_nonce->setAccessible( true );
$hash = md5( $real_tick . 'wp_async_async' . get_class( $async ) );
$nonce = substr( $hash, - 12, 10 );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 1,
'args' => array( $nonce_tick . 'wp_async_async' . get_class( $async ), 'nonce' ),
'return' => function ( $thing ) {
return md5( $thing );
}
) );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 1,
'args' => array( $real_tick . 'wp_async_async' . get_class( $async ), 'nonce' ),
'return' => function ( $thing ) {
return md5( $thing );
}
) );
$this->assertSame( 2, $verify_nonce->invoke( $async, $nonce ) );
$this->assertConditionsMet();
}
public function test_verify_async_nonce_nopriv() {
$async = $this->getMockAsync();
$nonce_tick = rand( 10, 99 );
WP_Mock::wpFunction( 'wp_nonce_tick', array(
'times' => 1,
'args' => array(),
'return' => $nonce_tick,
) );
$action = new ReflectionProperty( 'Async', 'action' );
$action->setAccessible( true );
$action->setValue( $async, 'nopriv_async' );
$verify_nonce = new ReflectionMethod( 'Async', 'verify_async_nonce' );
$verify_nonce->setAccessible( true );
$hash = md5( $nonce_tick . 'wp_async_async' . get_class( $async ) );
$nonce = substr( $hash, - 12, 10 );
WP_Mock::wpFunction( 'wp_hash', array(
'times' => 1,
'args' => array( $nonce_tick . 'wp_async_async' . get_class( $async ), 'nonce' ),
'return' => function ( $thing ) {
return md5( $thing );
}
) );
$this->assertSame( 1, $verify_nonce->invoke( $async, $nonce ) );
$this->assertConditionsMet();
}
/**
* Get a mock object for the async task class
*
* @param string $class The name of the class to mock
* @param array $methods Which methods to mock
* @param mixed $auth The auth level to simulate
*
* @return \Mockery\Mock
*/
private function getMockAsync( $class = 'Async', array $methods = array(), $auth = false ) {
$stub = '';
if ( ! empty( $methods ) ) {
$stub = '[' . implode( ',', $methods ) . ']';
}
$mockClass = "$class$stub";
/** @var \Mockery\Mock $mock */
$mock = Mockery::mock( $mockClass, array( $auth ) );
$mock->makePartial();
$mock->shouldAllowMockingProtectedMethods();
return $mock;
}
}

View File

@@ -0,0 +1,11 @@
<?php
class Async extends BaseAsync {
public function __construct( $init = parent::BOTH ) {
if ( $init ) {
parent::__construct( $init );
}
}
}

View File

@@ -0,0 +1,12 @@
<?php
class BaseAsync extends WP_Async_Task {
protected $action = 'async';
protected function prepare_data( $data ) {
}
protected function run_action() {
}
}

View File

@@ -0,0 +1,7 @@
<?php
class EmptyAsync extends BaseAsync {
protected $action = null;
}