Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions includes/Checker/AJAX_Runner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Class WordPress\Plugin_Check\Checker\AJAX_Runner
*
* @package plugin-check
*/

namespace WordPress\Plugin_Check\Checker;

use Exception;
use WordPress\Plugin_Check\Utilities\Plugin_Request_Utility;

/**
* AJAX Runner class.
*
* @since n.e.x.t
*/
class AJAX_Runner extends Abstract_Check_Runner {

/**
* An instance of the Checks class.
*
* @since n.e.x.t
* @var Checks
*/
protected $checks;

/**
* Checks if the current request is an AJAX request for the Plugin Checker.
*
* @since n.e.x.t
*
* @return bool Returns true if is an AJAX request for the plugin check else false.
*/
public function is_plugin_check() {
if ( ! wp_doing_ajax() ) {
return false;
}

if ( ! isset( $_REQUEST['action'] ) || 'plugin_check_run_checks' !== $_REQUEST['action'] ) {
return false;
}

return true;
}

/**
* Creates and returns an instance of the Checks class based on the request.
*
* @since n.e.x.t
*
* @return Checks An instance of the Checks class.
*
* @throws Exception Thrown if the plugin main file cannot be found based on the AJAX input.
*/
protected function get_checks_instance() {
if ( ! isset( $this->checks ) ) {
if ( ! isset( $_REQUEST['plugin'] ) ) {
throw new Exception( 'Invalid plugin slug: Plugin slug must not be empty.' );
}

// Get the plugin name from the AJAX request.
$plugin_file = Plugin_Request_Utility::get_plugin_basename_from_input( $_REQUEST['plugin'] );

$this->checks = new Checks( WP_PLUGIN_DIR . '/' . $plugin_file );
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that the plugin is always being loaded from inside the plugins directory rather than as an mu-plugin or required via code, as is often the case during unit testing setups, or some platform configurations. Is that an intentional limitation at this point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @joemcgill thats a great point and something we should probably look into in the future. For this initial milestone I believe the intention is to only check plugins found within the main plugin directory.

}

return $this->checks;
}

/**
* Returns an array of Check slugs to run based on the request.
*
* @since n.e.x.t
*
* @return array An array of Check slugs to run.
Comment thread
felixarntz marked this conversation as resolved.
*/
protected function get_check_slugs_to_run() {
$checks = array();

if ( isset( $_REQUEST['checks'] ) ) {
// Checks are passed as a comma separated string.
$checks = wp_parse_list( $_REQUEST['checks'] );
}

return $checks;
}
}
2 changes: 1 addition & 1 deletion includes/Checker/Abstract_Check_Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ abstract public function is_plugin_check();
*
* @since n.e.x.t
*
* @return Checks An instances of the Checks class.
* @return Checks An instance of the Checks class.
*/
abstract protected function get_checks_instance();

Expand Down
12 changes: 6 additions & 6 deletions includes/Checker/CLI_Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
class CLI_Runner extends Abstract_Check_Runner {

/**
* An instances of the Checks class.
* An instance of the Checks class.
*
* @since n.e.x.t
* @var Checks
Expand All @@ -29,7 +29,7 @@ class CLI_Runner extends Abstract_Check_Runner {
*
* @since n.e.x.t
*
* @return bool
* @return bool Returns true if is an CLI request for the plugin check else false.
*/
public function is_plugin_check() {
if ( empty( $_SERVER['argv'] ) || 3 > count( $_SERVER['argv'] ) ) {
Expand All @@ -48,11 +48,11 @@ public function is_plugin_check() {
}

/**
* Retruns an instance of the Checks class.
* Creates and returns an instance of the Checks class based on the request.
*
* @since n.e.x.t
*
* @return Checks
* @return Checks An instance of the Checks class.
*
* @throws Exception Thrown if the plugin main file cannot be found based on the CLI input.
*/
Expand All @@ -69,11 +69,11 @@ protected function get_checks_instance() {
}

/**
* Returns an array of Check instances to run.
* Returns an array of Check slugs to run based on the request.
*
* @since n.e.x.t
*
* @return array An array of Check instances to run.
* @return array An array of Check slugs to run.
*/
protected function get_check_slugs_to_run() {
$checks = array();
Expand Down
153 changes: 153 additions & 0 deletions tests/Checker/AJAX_Runner_Tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php
/**
* Tests for the Checks class.
*
* @package plugin-check
*/

use WordPress\Plugin_Check\Checker\AJAX_Runner;
use WordPress\Plugin_Check\Checker\Check_Result;

class AJAX_Runner_Tests extends WP_UnitTestCase {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an observation, but since all of these are filtering the wp_doing_ajax value, is there any value in moving this shared code to a fixture that runs before each test? I also wonder if there's any value in having a test case that confirms the expected behavior if wp_doing_ajax is false?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @joemcgill I've added an additional test to check behaviour when wp_doing_ajax is false. We can potentially refactor the tests here in future to reduce duplicate code here.


public function test_is_plugin_check_returns_true() {
// Mock the AJAX request.
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'plugin_check_run_checks';

$runner = new AJAX_Runner();

$this->assertTrue( $runner->is_plugin_check() );
}

public function test_is_plugin_check_returns_false() {
// Mock the AJAX request.
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'a_different_ajax_request';

$runner = new AJAX_Runner();

$this->assertFalse( $runner->is_plugin_check() );
}

public function test_is_plugin_check_returns_false_not_ajax() {
// Mock the AJAX request.
add_filter( 'wp_doing_ajax', '__return_false' );

$runner = new AJAX_Runner();

$this->assertFalse( $runner->is_plugin_check() );
}

public function test_prepare_with_runtime_check() {
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'plugin_check_run_checks';
$_REQUEST['plugin'] = 'plugin-check';
$_REQUEST['checks'] = 'runtime_check';

add_filter(
'wp_plugin_check_checks',
function( $checks ) {
return array(
'runtime_check' => new WordPress\Plugin_Check\Test_Data\Runtime_Check(),
);
}
);

$runner = new AJAX_Runner();
$cleanup = $runner->prepare();

$this->assertIsCallable( $cleanup );

// Assert the Universal_Runtume_Preparation was run.
$this->assertTrue( has_filter( 'option_active_plugins' ) );
$this->assertTrue( has_filter( 'default_option_active_plugins' ) );
$this->assertTrue( has_filter( 'stylesheet' ) );
$this->assertTrue( has_filter( 'template' ) );
$this->assertTrue( has_filter( 'pre_option_template' ) );
$this->assertTrue( has_filter( 'pre_option_stylesheet' ) );
$this->assertTrue( has_filter( 'pre_option_current_theme' ) );
$this->assertTrue( has_filter( 'pre_option_template_root' ) );
$this->assertTrue( has_filter( 'pre_option_stylesheet_root' ) );
}

public function test_prepare_with_static_check() {
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'plugin_check_run_checks';
$_REQUEST['plugin'] = 'plugin-check';
$_REQUEST['checks'] = 'empty_check';

add_filter(
'wp_plugin_check_checks',
function( $checks ) {
return array(
'empty_check' => new WordPress\Plugin_Check\Test_Data\Empty_Check(),
);
}
);

$runner = new AJAX_Runner();
$cleanup = $runner->prepare();

$this->assertIsCallable( $cleanup );

// Assert the Universal_Runtume_Preparation was not run.
$this->assertFalse( has_filter( 'option_active_plugins' ) );
$this->assertFalse( has_filter( 'default_option_active_plugins' ) );
$this->assertFalse( has_filter( 'stylesheet' ) );
$this->assertFalse( has_filter( 'template' ) );
$this->assertFalse( has_filter( 'pre_option_template' ) );
$this->assertFalse( has_filter( 'pre_option_stylesheet' ) );
$this->assertFalse( has_filter( 'pre_option_current_theme' ) );
$this->assertFalse( has_filter( 'pre_option_template_root' ) );
$this->assertFalse( has_filter( 'pre_option_stylesheet_root' ) );
}

public function test_run() {
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'plugin_check_run_checks';
$_REQUEST['plugin'] = 'plugin-check';
$_REQUEST['checks'] = 'empty_check';

add_filter(
'wp_plugin_check_checks',
function( $checks ) {
return array(
'empty_check' => new WordPress\Plugin_Check\Test_Data\Empty_Check(),
);
}
);

$runner = new AJAX_Runner();
$runner->prepare();
$results = $runner->run();

$this->assertInstanceOf( Check_Result::class, $results );
$this->assertEmpty( $results->get_warnings() );
$this->assertEmpty( $results->get_errors() );
}

public function test_run_with_errors() {
add_filter( 'wp_doing_ajax', '__return_true' );
$_REQUEST['action'] = 'plugin_check_run_checks';
$_REQUEST['plugin'] = 'plugin-check';
$_REQUEST['checks'] = 'error_check';

add_filter(
'wp_plugin_check_checks',
function( $checks ) {
return array(
'error_check' => new WordPress\Plugin_Check\Test_Data\Error_Check(),
);
}
);

$runner = new AJAX_Runner();
$runner->prepare();
$results = $runner->run();

$this->assertInstanceOf( Check_Result::class, $results );
$this->assertEmpty( $results->get_warnings() );
$this->assertNotEmpty( $results->get_errors() );
}
}