From 22619885305023c62a6c6a7b5c2c75b911f5f24c Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 19 May 2017 18:24:35 +0100 Subject: [PATCH 01/20] Prevent Exoskeleton dying when under test --- classes/class-exoskeleton.php | 7 +++++++ 1 file changed, 7 insertions(+) mode change 100644 => 100755 classes/class-exoskeleton.php diff --git a/classes/class-exoskeleton.php b/classes/class-exoskeleton.php old mode 100644 new mode 100755 index 9631da5..98d472a --- a/classes/class-exoskeleton.php +++ b/classes/class-exoskeleton.php @@ -292,7 +292,14 @@ private function back_off_please( $rule_id, $matched_rule, $lock ) { @header( "Cache-Control: public max-age=$retry_after" ); status_header( 429, 'Exoskeleton: too many requests for this endpoint. Please consult Retry-After and come back later. Meanwhile enjoy a well-deserved REST' ); @header( "Retry-After: $retry_after" ); + + // Do not die if testing. + if ( defined( 'PHPUNIT_EXOSKELETON_TESTING' ) ) { + throw new Exception('locked'); + } + die(); + } /** From 01a118e7d7062975acc7b0a49ca471cdad7a317f Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 19 May 2017 18:25:53 +0100 Subject: [PATCH 02/20] Test skeleton in place --- bin/install-wp-tests.sh | 127 ++++++++++++++++++++++++++++++++++++++++ phpcs.ruleset.xml | 10 ++++ phpunit.xml.dist | 24 ++++++++ tests/bootstrap.php | 25 ++++++++ 4 files changed, 186 insertions(+) create mode 100755 bin/install-wp-tests.sh create mode 100644 phpcs.ruleset.xml create mode 100755 phpunit.xml.dist create mode 100755 tests/bootstrap.php diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100755 index 0000000..73bb4c7 --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/phpcs.ruleset.xml b/phpcs.ruleset.xml new file mode 100644 index 0000000..210c25a --- /dev/null +++ b/phpcs.ruleset.xml @@ -0,0 +1,10 @@ + + + Generally-applicable sniffs for WordPress plugins + + + + + */node_modules/* + */vendor/* + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100755 index 0000000..f098dcf --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + ./tests/ + + + + + . + exoskeleton.php + classes/class-exoskeleton.php + + + + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100755 index 0000000..d289b47 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,25 @@ + Date: Fri, 19 May 2017 18:26:37 +0100 Subject: [PATCH 03/20] Add tests for the public rule adding methods --- tests/test-exoskeleton-add-rule.php | 166 ++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100755 tests/test-exoskeleton-add-rule.php diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php new file mode 100755 index 0000000..a6f03cb --- /dev/null +++ b/tests/test-exoskeleton-add-rule.php @@ -0,0 +1,166 @@ + '/wp/v2/posts', + 'window' => 5, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'any', + ); + + self::$three_valid_rules = array( + [ + 'route' => '/wp/v2/posts', + 'window' => 10, + 'limit' => 25, + 'lockout' => 200, + 'method' => 'any', + ],[ + 'route' => '/wp/v2/post/1', + 'window' => 100, + 'limit' => 5, + 'lockout' => 60, + 'method' => 'GET', + ],[ + 'route' => '/wp/v2/post/2', + 'window' => 90, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'GET', + ] + ); + } + + function setUp() { + $instance = Exoskeleton::get_instance(); + $instance->rules = []; + } + + /** + * Get instance of Exoskeleton + */ + function test_get_instance() { + $this->assertInstanceOf( Exoskeleton::class, Exoskeleton::get_instance() ); + } + + /** + * Test adding a valid rule + */ + function test_add_valid_rule() { + + $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); + } + + /** + * Test adding a valid rule - missing method + */ + function test_add_valid_rule_missing_method() { + $args = $this::$single_valid_rule; + unset( $args['method'] ); + + $this->assertTrue( exoskeleton_add_rule( $args ) ); + } + + /** + * Test adding an invalid rule - missing route + */ + function test_add_invalid_rule_missing_route() { + $args = $this::$single_valid_rule; + unset( $args['route'] ); + + $this->assertFalse( exoskeleton_add_rule( $args ) ); + } + + /** + * Test adding an invalid rule - missing window + */ + function test_add_invalid_rule_missing_window() { + $args = $this::$single_valid_rule; + unset( $args['window'] ); + + $this->assertFalse( exoskeleton_add_rule( $args ) ); + } + + /** + * Test adding an invalid rule - missing limit + */ + function test_add_invalid_rule_missing_limit() { + $args = $this::$single_valid_rule; + unset( $args['limit'] ); + + $this->assertFalse( exoskeleton_add_rule( $args ) ); + } + + /** + * Test adding an invalid rule - missing lockout + */ + function test_add_invalid_rule_missing_lockout() { + $args = $this::$single_valid_rule; + unset( $args['lockout'] ); + + $this->assertFalse( exoskeleton_add_rule( $args ) ); + } + + /** + * Test adding multiple valid rules + */ + function test_adding_multiple_valid_rules() { + $args = $this::$three_valid_rules; + + $this->assertNull( exoskeleton_add_rules( $args ) ); + $instance = Exoskeleton::get_instance(); + $this->assertEquals( 3, count( $instance->rules ) ); + } + + + /** + * Test adding multiple rules including invalid + */ + function test_adding_multiple_rules_including_invalid() { + $args = $this::$three_valid_rules; + unset( $args[1]['lockout'] ); + + $this->assertNull( exoskeleton_add_rules( $args ) ); + $instance = Exoskeleton::get_instance(); + $this->assertEquals( 2, count( $instance->rules ) ); + } + + /** + * Test adding a valid rule for a custom route + * Exoskeleton makes no check for existence of custom route. + */ + function test_add_valid_custom_route_rule() { + $args = $this::$single_valid_rule; + $args['route'] = '/custom/route/that/does/not/exist'; + + $this->assertTrue( exoskeleton_add_rule( $args ) ); + } + + /** + * Test route is not limited + */ + function test_route_is_not_limited() { + $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); + + } + +} From 42b12623181daa152fc520e2d40087905b9ee96e Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 19 May 2017 18:27:32 +0100 Subject: [PATCH 04/20] Start testing the REST API calls --- tests/test-exoskeleton-rest-api-calls.php | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 tests/test-exoskeleton-rest-api-calls.php diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php new file mode 100755 index 0000000..ef914dd --- /dev/null +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -0,0 +1,66 @@ + '/wp/v2/posts', + 'window' => 5, + 'limit' => 1, + 'lockout' => 5, + 'method' => 'any', + ); + } + + function setUp() { + parent::setUp(); + + // Clear existing rules. + $instance = Exoskeleton::get_instance(); + $instance->rules = []; + + exoskeleton_add_rule( $this::$single_valid_rule ); + + } + + /** + * Test route is not limited + */ + function test_route_is_not_limited() { + $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); + $response = rest_do_request($request); + $this->assertEquals( 200, $response->status ); + } + + /** + * Test route has been restricted + */ + function test_route_is_limited() { + $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); + $response = rest_do_request($request); + $this->assertEquals( 200, $response->status ); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('locked'); + $response = rest_do_request($request); + } + +} From ad55ba02146ac07a552d371c9c77c2e1dc97cfbf Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 19 May 2017 18:34:04 +0100 Subject: [PATCH 05/20] Remove test that had been moved to another class --- tests/test-exoskeleton-add-rule.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index a6f03cb..858e9d1 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -155,12 +155,5 @@ function test_add_valid_custom_route_rule() { $this->assertTrue( exoskeleton_add_rule( $args ) ); } - /** - * Test route is not limited - */ - function test_route_is_not_limited() { - $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); - - } } From bcadf1a0080a5340d69283a0bc327a1fbcdfc778 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 19 May 2017 18:37:07 +0100 Subject: [PATCH 06/20] Indentation change --- tests/test-exoskeleton-add-rule.php | 48 +++++++++++------------ tests/test-exoskeleton-rest-api-calls.php | 10 ++--- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index 858e9d1..bc99963 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -20,33 +20,33 @@ static function setUpBeforeClass() { parent::setUpBeforeClass(); self::$single_valid_rule = array( - 'route' => '/wp/v2/posts', - 'window' => 5, - 'limit' => 2, - 'lockout' => 30, - 'method' => 'any', + 'route' => '/wp/v2/posts', + 'window' => 5, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'any', ); self::$three_valid_rules = array( - [ - 'route' => '/wp/v2/posts', - 'window' => 10, - 'limit' => 25, - 'lockout' => 200, - 'method' => 'any', - ],[ - 'route' => '/wp/v2/post/1', - 'window' => 100, - 'limit' => 5, - 'lockout' => 60, - 'method' => 'GET', - ],[ - 'route' => '/wp/v2/post/2', - 'window' => 90, - 'limit' => 2, - 'lockout' => 30, - 'method' => 'GET', - ] + [ + 'route' => '/wp/v2/posts', + 'window' => 10, + 'limit' => 25, + 'lockout' => 200, + 'method' => 'any', + ],[ + 'route' => '/wp/v2/post/1', + 'window' => 100, + 'limit' => 5, + 'lockout' => 60, + 'method' => 'GET', + ],[ + 'route' => '/wp/v2/post/2', + 'window' => 90, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'GET', + ] ); } diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index ef914dd..e3f828a 100755 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -22,11 +22,11 @@ static function setUpBeforeClass() { parent::setUpBeforeClass(); self::$single_valid_rule = array( - 'route' => '/wp/v2/posts', - 'window' => 5, - 'limit' => 1, - 'lockout' => 5, - 'method' => 'any', + 'route' => '/wp/v2/posts', + 'window' => 5, + 'limit' => 1, + 'lockout' => 5, + 'method' => 'any', ); } From 3e23eae1e1ade1b77b660172250da4125ddecc13 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Mon, 22 May 2017 16:20:05 +0100 Subject: [PATCH 07/20] Added tests for window expiry and methods --- phpunit.xml.dist | 3 +- tests/test-exoskeleton-rest-api-calls.php | 139 ++++++++++++++++------ 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f098dcf..77950a8 100755 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,8 +12,7 @@ - - . + exoskeleton.php classes/class-exoskeleton.php diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index e3f828a..1257a95 100755 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -10,26 +10,6 @@ */ class ExoskeletonRestApiCallsTest extends WP_UnitTestCase { - protected static $single_valid_rule; - protected static $three_valid_rules; - - protected $server; - - /** - * Define some valid rule sets to reduce duplication in tests. - */ - static function setUpBeforeClass() { - parent::setUpBeforeClass(); - - self::$single_valid_rule = array( - 'route' => '/wp/v2/posts', - 'window' => 5, - 'limit' => 1, - 'lockout' => 5, - 'method' => 'any', - ); - } - function setUp() { parent::setUp(); @@ -37,30 +17,111 @@ function setUp() { $instance = Exoskeleton::get_instance(); $instance->rules = []; - exoskeleton_add_rule( $this::$single_valid_rule ); } /** - * Test route is not limited - */ - function test_route_is_not_limited() { - $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); - $response = rest_do_request($request); - $this->assertEquals( 200, $response->status ); - } + * Check that requests do get properly limited + * @dataProvider limitTestingProvider + */ + public function test_limits_are_applied( $rule, $test_method = false ) + { + if ( empty( $test_method ) ) { + $test_method = $rule['method']; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < $rule['limit'] ) { + $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + } + } + } + + + /** + * Make sure that requests falling outside the window do not get imited. + * @dataProvider limitTestingProvider + */ + public function test_limit_window_expiry( $rule, $test_method = false ) + { + if ( empty( $test_method ) ) { + $test_method = $rule['method']; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < $rule['limit'] ) { + $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + sleep( $rule['window'] + 1 ); + $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } + } + } /** - * Test route has been restricted - */ - function test_route_is_limited() { - $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); - $response = rest_do_request($request); - $this->assertEquals( 200, $response->status ); + * Limits should only be applied if the correct method is requested + * @dataProvider limitTestingProvider + */ + public function test_different_methods_not_limited( $rule, $test_method = false ) + { + // Change the test method + if ( empty( $test_method ) ) { + $test_method = 'HEAD'; + } else { + $this->assertNull( null ); // TODO Test third request with valid method + return; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } + } - $this->expectException(Exception::class); - $this->expectExceptionMessage('locked'); - $response = rest_do_request($request); - } -} + + public function limitTestingProvider() + { + return [ + [ + array( + 'route' => '/wp/v2/posts', + 'window' => 5, + 'limit' => 3, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ) + ], + [ + array( + 'route' => '/wp/v2/categories', + 'window' => 2, + 'limit' => 10, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ) + ], + [ + array( + 'route' => '/wp/v2/categories', + 'window' => 2, + 'limit' => 10, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => true, + ), + 'HEAD', + ] + ]; + } + +} \ No newline at end of file From 50360ae906085804f498db00a7d7b7c633b8e00f Mon Sep 17 00:00:00 2001 From: anigeluk Date: Tue, 23 May 2017 17:55:51 +0100 Subject: [PATCH 08/20] Remaining rest server tests --- tests/test-exoskeleton-add-rule.php | 2 +- tests/test-exoskeleton-rest-api-calls.php | 131 ++++++++++++++++------ 2 files changed, 100 insertions(+), 33 deletions(-) mode change 100755 => 100644 tests/test-exoskeleton-add-rule.php mode change 100755 => 100644 tests/test-exoskeleton-rest-api-calls.php diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php old mode 100755 new mode 100644 index bc99963..63de057 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -53,6 +53,7 @@ static function setUpBeforeClass() { function setUp() { $instance = Exoskeleton::get_instance(); $instance->rules = []; + } /** @@ -155,5 +156,4 @@ function test_add_valid_custom_route_rule() { $this->assertTrue( exoskeleton_add_rule( $args ) ); } - } diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php old mode 100755 new mode 100644 index 1257a95..7d9ddce --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -10,6 +10,7 @@ */ class ExoskeletonRestApiCallsTest extends WP_UnitTestCase { + function setUp() { parent::setUp(); @@ -17,27 +18,74 @@ function setUp() { $instance = Exoskeleton::get_instance(); $instance->rules = []; - + add_action( 'rest_api_init', function () { + register_rest_route( 'exoskeleton/v1', '/exoskeleton/(?P\d+)', array( + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], + ) ); + } ); } + + /** + * Test key generation + * @dataProvider limitTestingProvider + */ + public function test_key_generation( $rule, $test ) { + exoskeleton_add_rule( $rule ); + $exoskeleton = Exoskeleton::get_instance(); + $this->assertEquals( $test['key'], key($exoskeleton->rules) ); + } + + /** + * Simple method to provide a callback for custom routes during testing + * @param mixed $data + * @return mixed + */ + public function custom_route_callback( $data ) { + return $data; + } + + + function test_pre_registered_custom_route_rules() { + + add_action( 'rest_api_init', function () { + register_rest_route( 'exoskeleton/v1', '/test/(?P\d+)', array( + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], + 'exoskeleton' => [ 'window' => 10, 'limit' => 5, 'lockout' => 20 ], + ) ); + } ); + for( $request = 1; $request <= 6; ++$request ) { + if ( $request < 5 ) { + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + $this->assertEquals( 200, $response->status ); + } else { + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + } + } + } + + /** * Check that requests do get properly limited * @dataProvider limitTestingProvider */ - public function test_limits_are_applied( $rule, $test_method = false ) - { - if ( empty( $test_method ) ) { - $test_method = $rule['method']; + public function test_limits_are_applied( $rule, $test ) { + if ( empty( $test['method'] ) ) { + $test['method'] = $rule['method']; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { if ( $request < $rule['limit'] ) { - $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); } else { $this->expectException( Exception::class ); $this->expectExceptionMessage( 'locked' ); - $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); } } } @@ -47,19 +95,18 @@ public function test_limits_are_applied( $rule, $test_method = false ) * Make sure that requests falling outside the window do not get imited. * @dataProvider limitTestingProvider */ - public function test_limit_window_expiry( $rule, $test_method = false ) - { - if ( empty( $test_method ) ) { - $test_method = $rule['method']; + public function test_limit_window_expiry( $rule, $test ) { + if ( empty( $test['method'] ) ) { + $test['method'] = $rule['method']; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { if ( $request < $rule['limit'] ) { - $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); } else { sleep( $rule['window'] + 1 ); - $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); } } @@ -69,58 +116,78 @@ public function test_limit_window_expiry( $rule, $test_method = false ) * Limits should only be applied if the correct method is requested * @dataProvider limitTestingProvider */ - public function test_different_methods_not_limited( $rule, $test_method = false ) - { + public function test_different_methods_not_limited( $rule, $test ) { // Change the test method - if ( empty( $test_method ) ) { - $test_method = 'HEAD'; + if ( empty( $test['method'] ) ) { + $test['method'] = 'HEAD'; } else { - $this->assertNull( null ); // TODO Test third request with valid method - return; + $rule['method'] = 'POST'; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { - $response = rest_do_request( new WP_REST_Request( $test_method, $rule['route'] ) ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); } } - public function limitTestingProvider() - { + + public function limitTestingProvider() { return [ [ - array( + 'rule' => [ 'route' => '/wp/v2/posts', - 'window' => 5, + 'window' => 1, 'limit' => 3, 'lockout' => 5, 'method' => 'GET', 'treat_head_like_get' => false, - ) + ], + 'test' => [ + 'key' => '01dd28291a6b5b95802281831ec3d6f5_GET', + ] ], [ - array( + 'rule' => [ 'route' => '/wp/v2/categories', 'window' => 2, - 'limit' => 10, + 'limit' => 6, 'lockout' => 5, 'method' => 'GET', 'treat_head_like_get' => false, - ) + ], + 'test' => [ + 'key' => 'fd568c1eb104fbad04765b9f2f0100ed_GET', + ] ], [ - array( + 'rule' => [ 'route' => '/wp/v2/categories', 'window' => 2, 'limit' => 10, 'lockout' => 5, 'method' => 'GET', 'treat_head_like_get' => true, - ), - 'HEAD', - ] + ], + 'test' => [ + 'method' => 'HEAD', + 'key' => '6ae5a5054b0a306061f10b9c1b193183_GET', + ] + ], + [ + 'rule' => [ + 'route' => '/exoskeleton/v1/exoskeleton/10', + 'window' => 1, + 'limit' => 3, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ], + 'test' => [ + 'key' => 'b09a74d523deb0962e21cafaadc63679_GET', + ] + ], ]; } From e9f2ac6bf54008668a4adb673d0a701df3ba0b74 Mon Sep 17 00:00:00 2001 From: anigeluk Date: Tue, 23 May 2017 18:11:28 +0100 Subject: [PATCH 09/20] Code linted and commented. --- tests/bootstrap.php | 2 +- tests/test-exoskeleton-add-rule.php | 20 ++++- tests/test-exoskeleton-rest-api-calls.php | 103 ++++++++++++++-------- 3 files changed, 83 insertions(+), 42 deletions(-) mode change 100755 => 100644 tests/bootstrap.php diff --git a/tests/bootstrap.php b/tests/bootstrap.php old mode 100755 new mode 100644 index d289b47..09b7457 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,7 +7,7 @@ $_tests_dir = getenv( 'WP_TESTS_DIR' ); if ( ! $_tests_dir ) { - $_tests_dir = dirname( __FILE__ ).'/../../../../wordpress-tests-lib'; + $_tests_dir = dirname( __FILE__ ) . '/../../../../wordpress-tests-lib'; } // Give access to tests_add_filter() function. diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index 63de057..011ec15 100644 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -10,7 +10,18 @@ */ class ExoskeletonAddRuleTest extends WP_UnitTestCase { + /** + * One rule definition + * + * @var array A single valid rule definition + */ protected static $single_valid_rule; + + /** + * Three rule definitions + * + * @var array Three valid rule definitions + */ protected static $three_valid_rules; /** @@ -46,15 +57,18 @@ static function setUpBeforeClass() { 'limit' => 2, 'lockout' => 30, 'method' => 'GET', - ] + ], ); } - function setUp() { + /** + * Pre test setup + */ + function setUp() { $instance = Exoskeleton::get_instance(); $instance->rules = []; - } + } /** * Get instance of Exoskeleton diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index 7d9ddce..9721100 100644 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -11,7 +11,10 @@ class ExoskeletonRestApiCallsTest extends WP_UnitTestCase { - function setUp() { + /** + * Pre test setup + */ + function setUp() { parent::setUp(); // Clear existing rules. @@ -20,43 +23,54 @@ function setUp() { add_action( 'rest_api_init', function () { register_rest_route( 'exoskeleton/v1', '/exoskeleton/(?P\d+)', array( - 'methods' => 'GET', - 'callback' => [ $this, 'custom_route_callback' ], + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], ) ); } ); - } + } /** * Test key generation - * @dataProvider limitTestingProvider - */ + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ public function test_key_generation( $rule, $test ) { exoskeleton_add_rule( $rule ); $exoskeleton = Exoskeleton::get_instance(); - $this->assertEquals( $test['key'], key($exoskeleton->rules) ); + $this->assertEquals( $test['key'], key( $exoskeleton->rules ) ); } /** * Simple method to provide a callback for custom routes during testing - * @param mixed $data - * @return mixed + * + * @param mixed $data data passed into the request for this route. + * @return mixed returns the data that was input to provide a valid output. */ public function custom_route_callback( $data ) { return $data; } + /** + * Check limits are honoured if a route is registered with exoskeleton limits. + */ function test_pre_registered_custom_route_rules() { add_action( 'rest_api_init', function () { register_rest_route( 'exoskeleton/v1', '/test/(?P\d+)', array( - 'methods' => 'GET', - 'callback' => [ $this, 'custom_route_callback' ], - 'exoskeleton' => [ 'window' => 10, 'limit' => 5, 'lockout' => 20 ], + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], + 'exoskeleton' => [ + 'window' => 10, + 'limit' => 5, + 'lockout' => 20, + ], ) ); } ); - for( $request = 1; $request <= 6; ++$request ) { + for ( $request = 1; $request <= 6; ++$request ) { if ( $request < 5 ) { $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); $this->assertEquals( 200, $response->status ); @@ -71,14 +85,17 @@ function test_pre_registered_custom_route_rules() { /** * Check that requests do get properly limited - * @dataProvider limitTestingProvider - */ - public function test_limits_are_applied( $rule, $test ) { + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limits_are_applied( $rule, $test ) { if ( empty( $test['method'] ) ) { $test['method'] = $rule['method']; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { if ( $request < $rule['limit'] ) { $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); @@ -88,19 +105,22 @@ public function test_limits_are_applied( $rule, $test ) { $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); } } - } + } /** - * Make sure that requests falling outside the window do not get imited. - * @dataProvider limitTestingProvider - */ - public function test_limit_window_expiry( $rule, $test ) { + * Make sure that requests falling outside the window do not get limited. + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limit_window_expiry( $rule, $test ) { if ( empty( $test['method'] ) ) { $test['method'] = $rule['method']; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { if ( $request < $rule['limit'] ) { $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); @@ -110,31 +130,38 @@ public function test_limit_window_expiry( $rule, $test ) { $this->assertEquals( 200, $response->status ); } } - } + } /** * Limits should only be applied if the correct method is requested - * @dataProvider limitTestingProvider - */ - public function test_different_methods_not_limited( $rule, $test ) { - // Change the test method + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_different_methods_not_limited( $rule, $test ) { + // Change the test or rule method for testing. if ( empty( $test['method'] ) ) { $test['method'] = 'HEAD'; } else { $rule['method'] = 'POST'; } $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); } - } - + } + /** + * Data provider + * + * @return array exoskeleton rule information and test data. + */ public function limitTestingProvider() { - return [ + return [ [ 'rule' => [ 'route' => '/wp/v2/posts', @@ -146,7 +173,7 @@ public function limitTestingProvider() { ], 'test' => [ 'key' => '01dd28291a6b5b95802281831ec3d6f5_GET', - ] + ], ], [ 'rule' => [ @@ -159,7 +186,7 @@ public function limitTestingProvider() { ], 'test' => [ 'key' => 'fd568c1eb104fbad04765b9f2f0100ed_GET', - ] + ], ], [ 'rule' => [ @@ -173,7 +200,7 @@ public function limitTestingProvider() { 'test' => [ 'method' => 'HEAD', 'key' => '6ae5a5054b0a306061f10b9c1b193183_GET', - ] + ], ], [ 'rule' => [ @@ -186,9 +213,9 @@ public function limitTestingProvider() { ], 'test' => [ 'key' => 'b09a74d523deb0962e21cafaadc63679_GET', - ] + ], ], ]; - } + } -} \ No newline at end of file +} From c28e5d537b61586df8d6fa7771ca762ad0cf309d Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Tue, 23 May 2017 18:43:10 +0100 Subject: [PATCH 10/20] Added check for overwriting existing rules --- tests/test-exoskeleton-add-rule.php | 9 +++++++++ 1 file changed, 9 insertions(+) mode change 100644 => 100755 tests/test-exoskeleton-add-rule.php diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php old mode 100644 new mode 100755 index 011ec15..8eefff7 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -170,4 +170,13 @@ function test_add_valid_custom_route_rule() { $this->assertTrue( exoskeleton_add_rule( $args ) ); } + /** + * Check that exoskeleton will not overwrite an already existing rule + */ + function test_internal_add_rule_method_fails_when_adding_existing_rule() { + $exoskeleton = Exoskeleton::get_instance(); + $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); + $this->assertFalse( $exoskeleton->add_rule( $this::$single_valid_rule ) ); + } + } From 457a902a4cd1b81cc1265ed46b71dc2a44c9aa37 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Tue, 23 May 2017 18:53:56 +0100 Subject: [PATCH 11/20] Added tests of internal rule validation method --- tests/test-exoskeleton-add-rule.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index 8eefff7..2526e58 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -36,6 +36,7 @@ static function setUpBeforeClass() { 'limit' => 2, 'lockout' => 30, 'method' => 'any', + 'treat_head_like_get' => false, ); self::$three_valid_rules = array( @@ -179,4 +180,22 @@ function test_internal_add_rule_method_fails_when_adding_existing_rule() { $this->assertFalse( $exoskeleton->add_rule( $this::$single_valid_rule ) ); } + /** + * Check that exoskeleton validates rules with false rule + */ + function test_internal_validate_rule_method_fails_when_adding_invalid_rule() { + $exoskeleton = Exoskeleton::get_instance(); + $invalid_rule = $this::$single_valid_rule; + unset( $invalid_rule['method'] ); + $this->assertFalse( $exoskeleton->validate_rule( $invalid_rule ) ); + } + + /** + * Check that exoskeleton validates rules with valid rule + */ + function test_internal_validate_rule_method_fails_when_adding_a_valid_rule() { + $exoskeleton = Exoskeleton::get_instance(); + $this->assertTrue( $exoskeleton->validate_rule( $this::$single_valid_rule ) ); + } + } From f9969d603ca7b60a6d71a2fd179ac4db28aecb19 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 09:09:59 +0100 Subject: [PATCH 12/20] refactored to use data provider --- tests/test-exoskeleton-add-rule.php | 146 +++++++------- tests/test-exoskeleton-rule_validation.php | 221 +++++++++++++++++++++ 2 files changed, 299 insertions(+), 68 deletions(-) create mode 100644 tests/test-exoskeleton-rule_validation.php diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index 2526e58..0f389db 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -80,69 +80,33 @@ function test_get_instance() { /** * Test adding a valid rule + * + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_rule() { - - $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); - } - - /** - * Test adding a valid rule - missing method - */ - function test_add_valid_rule_missing_method() { - $args = $this::$single_valid_rule; - unset( $args['method'] ); - - $this->assertTrue( exoskeleton_add_rule( $args ) ); - } - - /** - * Test adding an invalid rule - missing route - */ - function test_add_invalid_rule_missing_route() { - $args = $this::$single_valid_rule; - unset( $args['route'] ); - - $this->assertFalse( exoskeleton_add_rule( $args ) ); - } - - /** - * Test adding an invalid rule - missing window - */ - function test_add_invalid_rule_missing_window() { - $args = $this::$single_valid_rule; - unset( $args['window'] ); - - $this->assertFalse( exoskeleton_add_rule( $args ) ); - } - - /** - * Test adding an invalid rule - missing limit - */ - function test_add_invalid_rule_missing_limit() { - $args = $this::$single_valid_rule; - unset( $args['limit'] ); + function test_add_valid_rule($rule) { - $this->assertFalse( exoskeleton_add_rule( $args ) ); + $this->assertTrue( exoskeleton_add_rule( $rule ) ); } /** - * Test adding an invalid rule - missing lockout + * Test adding rule fails if any required field is missing + * + * @dataProvider requiredFieldsProvider + * @param string $field required field names. */ - function test_add_invalid_rule_missing_lockout() { - $args = $this::$single_valid_rule; - unset( $args['lockout'] ); - - $this->assertFalse( exoskeleton_add_rule( $args ) ); + public function test_adding_rule_fails_missing_required_fields( $field ) { + $rule = $this->getValidRuleHelper(); + unset( $rule[ $field ] ); + $this->assertFalse( exoskeleton_add_rule( $rule ) ); } /** * Test adding multiple valid rules */ function test_adding_multiple_valid_rules() { - $args = $this::$three_valid_rules; - - $this->assertNull( exoskeleton_add_rules( $args ) ); + $ruleset = $this->validRuleProvider(); + $this->assertNull( exoskeleton_add_rules( $ruleset[0] ) ); $instance = Exoskeleton::get_instance(); $this->assertEquals( 3, count( $instance->rules ) ); } @@ -163,39 +127,85 @@ function test_adding_multiple_rules_including_invalid() { /** * Test adding a valid rule for a custom route * Exoskeleton makes no check for existence of custom route. + * + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_custom_route_rule() { - $args = $this::$single_valid_rule; - $args['route'] = '/custom/route/that/does/not/exist'; + function test_add_valid_custom_route_rule($rule) { + $rule['route'] = '/custom/route/that/does/not/exist'; - $this->assertTrue( exoskeleton_add_rule( $args ) ); + $this->assertTrue( exoskeleton_add_rule( $rule ) ); } /** * Check that exoskeleton will not overwrite an already existing rule + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. */ - function test_internal_add_rule_method_fails_when_adding_existing_rule() { + function test_internal_add_rule_method_fails_when_adding_existing_rule($rule) { $exoskeleton = Exoskeleton::get_instance(); - $this->assertTrue( exoskeleton_add_rule( $this::$single_valid_rule ) ); - $this->assertFalse( $exoskeleton->add_rule( $this::$single_valid_rule ) ); + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + $this->assertFalse( $exoskeleton->add_rule( $rule ) ); + } + + + /** + * Provides required fields for exoskeleton_add_rule + * Both the method and treat_head_like_get fields are defaulted + * when called via the add_rule method so are not required here + * + * @return array Valid exoskeleton rule methods + */ + public function requiredFieldsProvider() { + return [ + [ 'route' ], + [ 'window' ], + [ 'limit' ], + [ 'lockout' ], + ]; } + /** - * Check that exoskeleton validates rules with false rule + * Provides valid exoskeleton rule definitions + * + * @return array Valid exoskeleton rule methods */ - function test_internal_validate_rule_method_fails_when_adding_invalid_rule() { - $exoskeleton = Exoskeleton::get_instance(); - $invalid_rule = $this::$single_valid_rule; - unset( $invalid_rule['method'] ); - $this->assertFalse( $exoskeleton->validate_rule( $invalid_rule ) ); + public function validRuleProvider() { + return [ + [ + [ + 'route' => '/wp/v2/posts', + 'window' => 10, + 'limit' => 25, + 'lockout' => 200, + 'method' => 'any', + ],[ + 'route' => '/wp/v2/post/1', + 'window' => 100, + 'limit' => 5, + 'lockout' => 60, + 'method' => 'GET', + ],[ + 'route' => '/wp/v2/post/2', + 'window' => 90, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'GET', + ], + ], + ]; } /** - * Check that exoskeleton validates rules with valid rule + * Get a single valid rule definition from the validRuleProvider + * + * @return array Valid Exoskeleton rule definition */ - function test_internal_validate_rule_method_fails_when_adding_a_valid_rule() { - $exoskeleton = Exoskeleton::get_instance(); - $this->assertTrue( $exoskeleton->validate_rule( $this::$single_valid_rule ) ); + public function getValidRuleHelper() { + $rules = $this->validRuleProvider(); + return $rules[0][0]; + } } diff --git a/tests/test-exoskeleton-rule_validation.php b/tests/test-exoskeleton-rule_validation.php new file mode 100644 index 0000000..9721100 --- /dev/null +++ b/tests/test-exoskeleton-rule_validation.php @@ -0,0 +1,221 @@ +rules = []; + + add_action( 'rest_api_init', function () { + register_rest_route( 'exoskeleton/v1', '/exoskeleton/(?P\d+)', array( + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], + ) ); + } ); + } + + + /** + * Test key generation + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_key_generation( $rule, $test ) { + exoskeleton_add_rule( $rule ); + $exoskeleton = Exoskeleton::get_instance(); + $this->assertEquals( $test['key'], key( $exoskeleton->rules ) ); + } + + /** + * Simple method to provide a callback for custom routes during testing + * + * @param mixed $data data passed into the request for this route. + * @return mixed returns the data that was input to provide a valid output. + */ + public function custom_route_callback( $data ) { + return $data; + } + + + /** + * Check limits are honoured if a route is registered with exoskeleton limits. + */ + function test_pre_registered_custom_route_rules() { + + add_action( 'rest_api_init', function () { + register_rest_route( 'exoskeleton/v1', '/test/(?P\d+)', array( + 'methods' => 'GET', + 'callback' => [ $this, 'custom_route_callback' ], + 'exoskeleton' => [ + 'window' => 10, + 'limit' => 5, + 'lockout' => 20, + ], + ) ); + } ); + for ( $request = 1; $request <= 6; ++$request ) { + if ( $request < 5 ) { + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + $this->assertEquals( 200, $response->status ); + } else { + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + } + } + } + + + /** + * Check that requests do get properly limited + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limits_are_applied( $rule, $test ) { + if ( empty( $test['method'] ) ) { + $test['method'] = $rule['method']; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < $rule['limit'] ) { + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + } + } + } + + + /** + * Make sure that requests falling outside the window do not get limited. + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limit_window_expiry( $rule, $test ) { + if ( empty( $test['method'] ) ) { + $test['method'] = $rule['method']; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < $rule['limit'] ) { + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + sleep( $rule['window'] + 1 ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } + } + } + + /** + * Limits should only be applied if the correct method is requested + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_different_methods_not_limited( $rule, $test ) { + // Change the test or rule method for testing. + if ( empty( $test['method'] ) ) { + $test['method'] = 'HEAD'; + } else { + $rule['method'] = 'POST'; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } + } + + + + /** + * Data provider + * + * @return array exoskeleton rule information and test data. + */ + public function limitTestingProvider() { + return [ + [ + 'rule' => [ + 'route' => '/wp/v2/posts', + 'window' => 1, + 'limit' => 3, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ], + 'test' => [ + 'key' => '01dd28291a6b5b95802281831ec3d6f5_GET', + ], + ], + [ + 'rule' => [ + 'route' => '/wp/v2/categories', + 'window' => 2, + 'limit' => 6, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ], + 'test' => [ + 'key' => 'fd568c1eb104fbad04765b9f2f0100ed_GET', + ], + ], + [ + 'rule' => [ + 'route' => '/wp/v2/categories', + 'window' => 2, + 'limit' => 10, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => true, + ], + 'test' => [ + 'method' => 'HEAD', + 'key' => '6ae5a5054b0a306061f10b9c1b193183_GET', + ], + ], + [ + 'rule' => [ + 'route' => '/exoskeleton/v1/exoskeleton/10', + 'window' => 1, + 'limit' => 3, + 'lockout' => 5, + 'method' => 'GET', + 'treat_head_like_get' => false, + ], + 'test' => [ + 'key' => 'b09a74d523deb0962e21cafaadc63679_GET', + ], + ], + ]; + } + +} From 61f70f1ca327a65ce74701865f8367e53f1d3c6e Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 09:10:50 +0100 Subject: [PATCH 13/20] Check rules on any method apply across different methods --- tests/test-exoskeleton-rest-api-calls.php | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) mode change 100644 => 100755 tests/test-exoskeleton-rest-api-calls.php diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php old mode 100644 new mode 100755 index 9721100..bf8bf27 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -153,6 +153,35 @@ public function test_different_methods_not_limited( $rule, $test ) { } } + /** + * Limits should apply across methods if 'any' is the rule method + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limits_apply_across_methods_for_any( $rule, $test ) { + // make sure the created rule applies to any method + $rule['method'] = 'any'; + if ( empty( $test['method'] ) ) { + $test['method'] = 'HEAD'; + } + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < $rule['limit'] ) { + // Initially test up to limit with one method + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + // Now call a different method and ensure it is limited + $test['method'] = 'post'; + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + } + } + } + /** From 5b9604aed1b08fea97d4a21aa6500c83f91804a9 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 09:11:18 +0100 Subject: [PATCH 14/20] Add rule validation tests --- tests/test-exoskeleton-rule_validation.php | 250 ++++++++------------- 1 file changed, 93 insertions(+), 157 deletions(-) mode change 100644 => 100755 tests/test-exoskeleton-rule_validation.php diff --git a/tests/test-exoskeleton-rule_validation.php b/tests/test-exoskeleton-rule_validation.php old mode 100644 new mode 100755 index 9721100..a7e5c94 --- a/tests/test-exoskeleton-rule_validation.php +++ b/tests/test-exoskeleton-rule_validation.php @@ -8,214 +8,150 @@ /** * Sample test case. */ -class ExoskeletonRestApiCallsTest extends WP_UnitTestCase { - +class ExoskeletonRuleValidationTest extends WP_UnitTestCase { /** * Pre test setup */ function setUp() { - parent::setUp(); - - // Clear existing rules. - $instance = Exoskeleton::get_instance(); - $instance->rules = []; - - add_action( 'rest_api_init', function () { - register_rest_route( 'exoskeleton/v1', '/exoskeleton/(?P\d+)', array( - 'methods' => 'GET', - 'callback' => [ $this, 'custom_route_callback' ], - ) ); - } ); + parent::setUp(); + // Clear existing rules. + $instance = Exoskeleton::get_instance(); + $instance->rules = []; } /** - * Test key generation + * Check that exoskeleton validates rules with an invalid rule * - * @dataProvider limitTestingProvider - * @param array $rule exoskeleton rule array. - * @param array $test extra test data. + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. */ - public function test_key_generation( $rule, $test ) { - exoskeleton_add_rule( $rule ); + function test_validate_rule_fails_when_adding_invalid_rule( $rule ) { $exoskeleton = Exoskeleton::get_instance(); - $this->assertEquals( $test['key'], key( $exoskeleton->rules ) ); + unset( $rule['method'] ); + $this->assertFalse( $exoskeleton->validate_rule( $rule ) ); } /** - * Simple method to provide a callback for custom routes during testing + * Check that exoskeleton validates rules with valid rule * - * @param mixed $data data passed into the request for this route. - * @return mixed returns the data that was input to provide a valid output. + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. */ - public function custom_route_callback( $data ) { - return $data; + function test_validate_rule_passes_when_adding_a_valid_rule( $rule ) { + $exoskeleton = Exoskeleton::get_instance(); + $this->assertTrue( $exoskeleton->validate_rule( $rule ) ); } /** - * Check limits are honoured if a route is registered with exoskeleton limits. + * Test validation passes available methods + * + * @dataProvider validationMethodsProvider + * @param string $method Valid method name for validation. */ - function test_pre_registered_custom_route_rules() { - - add_action( 'rest_api_init', function () { - register_rest_route( 'exoskeleton/v1', '/test/(?P\d+)', array( - 'methods' => 'GET', - 'callback' => [ $this, 'custom_route_callback' ], - 'exoskeleton' => [ - 'window' => 10, - 'limit' => 5, - 'lockout' => 20, - ], - ) ); - } ); - for ( $request = 1; $request <= 6; ++$request ) { - if ( $request < 5 ) { - $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); - $this->assertEquals( 200, $response->status ); - } else { - $this->expectException( Exception::class ); - $this->expectExceptionMessage( 'locked' ); - $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); - } - } + public function test_validate_rule_passes_all_available_methods( $method ) { + $rule = $this->getValidRuleHelper(); + $rule['method'] = $method; + $exoskeleton = Exoskeleton::get_instance(); + $this->assertTrue( $this->invokeMethod( $exoskeleton, 'validate_rule', array( $rule ) ) ); } - /** - * Check that requests do get properly limited + * Test validation fails is any required field is missing * - * @dataProvider limitTestingProvider - * @param array $rule exoskeleton rule array. - * @param array $test extra test data. + * @dataProvider requiredFieldsProvider + * @param string $field required field names. */ - public function test_limits_are_applied( $rule, $test ) { - if ( empty( $test['method'] ) ) { - $test['method'] = $rule['method']; - } - $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { - if ( $request < $rule['limit'] ) { - $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); - $this->assertEquals( 200, $response->status ); - } else { - $this->expectException( Exception::class ); - $this->expectExceptionMessage( 'locked' ); - $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); - } - } + public function test_validate_rule_fails_missing_required_fields( $field ) { + $rule = $this->getValidRuleHelper(); + unset( $rule[ $field ] ); + $exoskeleton = Exoskeleton::get_instance(); + $this->assertFalse( $this->invokeMethod( $exoskeleton, 'validate_rule', array( $rule ) ) ); } /** - * Make sure that requests falling outside the window do not get limited. + * Data provider * - * @dataProvider limitTestingProvider - * @param array $rule exoskeleton rule array. - * @param array $test extra test data. + * @return array Valid exoskeleton rule methods */ - public function test_limit_window_expiry( $rule, $test ) { - if ( empty( $test['method'] ) ) { - $test['method'] = $rule['method']; - } - $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { - if ( $request < $rule['limit'] ) { - $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); - $this->assertEquals( 200, $response->status ); - } else { - sleep( $rule['window'] + 1 ); - $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); - $this->assertEquals( 200, $response->status ); - } - } + public function validationMethodsProvider() { + return [ + [ 'GET' ], + [ 'POST' ], + [ 'PUT' ], + [ 'PATCH' ], + [ 'DELETE' ], + [ 'HEAD' ], + [ 'any' ], + ]; } /** - * Limits should only be applied if the correct method is requested + * Data provider * - * @dataProvider limitTestingProvider - * @param array $rule exoskeleton rule array. - * @param array $test extra test data. + * @return array Valid exoskeleton rule methods */ - public function test_different_methods_not_limited( $rule, $test ) { - // Change the test or rule method for testing. - if ( empty( $test['method'] ) ) { - $test['method'] = 'HEAD'; - } else { - $rule['method'] = 'POST'; - } - $this->assertTrue( exoskeleton_add_rule( $rule ) ); - for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { - $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); - $this->assertEquals( 200, $response->status ); - } + public function requiredFieldsProvider() { + return [ + [ 'route' ], + [ 'method' ], + [ 'window' ], + [ 'limit' ], + [ 'lockout' ], + [ 'treat_head_like_get' ], + ]; } - - /** * Data provider * - * @return array exoskeleton rule information and test data. + * @return array Valid exoskeleton rule methods */ - public function limitTestingProvider() { + public function validRuleProvider() { return [ [ - 'rule' => [ + [ 'route' => '/wp/v2/posts', - 'window' => 1, - 'limit' => 3, - 'lockout' => 5, - 'method' => 'GET', - 'treat_head_like_get' => false, - ], - 'test' => [ - 'key' => '01dd28291a6b5b95802281831ec3d6f5_GET', - ], - ], - [ - 'rule' => [ - 'route' => '/wp/v2/categories', - 'window' => 2, - 'limit' => 6, - 'lockout' => 5, - 'method' => 'GET', - 'treat_head_like_get' => false, - ], - 'test' => [ - 'key' => 'fd568c1eb104fbad04765b9f2f0100ed_GET', - ], - ], - [ - 'rule' => [ - 'route' => '/wp/v2/categories', - 'window' => 2, - 'limit' => 10, - 'lockout' => 5, - 'method' => 'GET', - 'treat_head_like_get' => true, - ], - 'test' => [ - 'method' => 'HEAD', - 'key' => '6ae5a5054b0a306061f10b9c1b193183_GET', - ], - ], - [ - 'rule' => [ - 'route' => '/exoskeleton/v1/exoskeleton/10', - 'window' => 1, - 'limit' => 3, - 'lockout' => 5, - 'method' => 'GET', + 'window' => 5, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'any', 'treat_head_like_get' => false, ], - 'test' => [ - 'key' => 'b09a74d523deb0962e21cafaadc63679_GET', - ], ], ]; } + /** + * Get a single valid rule definition from the validRuleProvider + * + * @return array Valid Exoskeleton rule definition + */ + public function getValidRuleHelper() { + $rules = $this->validRuleProvider(); + return $rules[0][0]; + + } + + + /** + * Call protected/private method of a class. + * + * @param object $object Instantiated object that we will run method on. + * @param string $method_name Method name to call. + * @param array $parameters Array of parameters to pass into method. + * + * @return mixed Method return. + */ + public function invokeMethod( &$object, $method_name, array $parameters = array() ) { + $reflection = new \ReflectionClass( get_class( $object ) ); + $method = $reflection->getMethod( $method_name ); + $method->setAccessible( true ); + + return $method->invokeArgs( $object, $parameters ); + } + } From b7bb0f4f18bf46fdb7233b3147e376f16b80fb0a Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 18:44:09 +0100 Subject: [PATCH 15/20] added testing of invalid data types for rules --- tests/test-exoskeleton-rule_validation.php | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/test-exoskeleton-rule_validation.php b/tests/test-exoskeleton-rule_validation.php index a7e5c94..2aed913 100755 --- a/tests/test-exoskeleton-rule_validation.php +++ b/tests/test-exoskeleton-rule_validation.php @@ -71,6 +71,19 @@ public function test_validate_rule_fails_missing_required_fields( $field ) { $this->assertFalse( $this->invokeMethod( $exoskeleton, 'validate_rule', array( $rule ) ) ); } + /** + * Test validation fails if fields contain invalid data + * + * @dataProvider invalidFieldValueProvider + * @param string $field array field_name => invalid_test_value. + */ + public function test_validate_rule_fails_invalid_field_values( $field ) { + $rule = $this->getValidRuleHelper(); + $rule[ key( $field ) ] = $field; + $exoskeleton = Exoskeleton::get_instance(); + $this->assertFalse( $this->invokeMethod( $exoskeleton, 'validate_rule', array( $rule ) ) ); + } + /** * Data provider @@ -105,6 +118,34 @@ public function requiredFieldsProvider() { ]; } + /** + * Data provider + * + * @return array Valid exoskeleton rule methods + */ + public function invalidFieldValueProvider() { + return [ + [ + [ 'window' => 'string', ], + ], + [ + [ 'window' => -1, ], + ], + [ + [ 'limit' => 'string', ], + ], + [ + [ 'limit' => -1, ], + ], + [ + [ 'lockout' => 'string', ], + ], + [ + [ 'treat_head_like_get' => 'string', ], + ], + ]; + } + /** * Data provider * From cad881668663d9ae08944a79cddd6b1bb43426da Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 22:09:38 +0100 Subject: [PATCH 16/20] add tests for process_custom_routes rule with multiple methods and use data provider --- tests/test-exoskeleton-add-rule.php | 112 ++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 22 deletions(-) diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index 0f389db..a013cad 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -10,12 +10,6 @@ */ class ExoskeletonAddRuleTest extends WP_UnitTestCase { - /** - * One rule definition - * - * @var array A single valid rule definition - */ - protected static $single_valid_rule; /** * Three rule definitions @@ -30,15 +24,6 @@ class ExoskeletonAddRuleTest extends WP_UnitTestCase { static function setUpBeforeClass() { parent::setUpBeforeClass(); - self::$single_valid_rule = array( - 'route' => '/wp/v2/posts', - 'window' => 5, - 'limit' => 2, - 'lockout' => 30, - 'method' => 'any', - 'treat_head_like_get' => false, - ); - self::$three_valid_rules = array( [ 'route' => '/wp/v2/posts', @@ -84,11 +69,22 @@ function test_get_instance() { * @dataProvider validRuleProvider * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_rule($rule) { + function test_add_valid_rule( $rule ) { $this->assertTrue( exoskeleton_add_rule( $rule ) ); } + /** + * Test adding a valid rule with multiple methods + * + * @dataProvider validRuleProvider + * @param array $rule Valid exoskeleton rule definition. + */ + function test_add_valid_rule_with_multiple_methods( $rule ) { + $rule['method'] = 'POST,GET'; + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + } + /** * Test adding rule fails if any required field is missing * @@ -116,10 +112,9 @@ function test_adding_multiple_valid_rules() { * Test adding multiple rules including invalid */ function test_adding_multiple_rules_including_invalid() { - $args = $this::$three_valid_rules; - unset( $args[1]['lockout'] ); - - $this->assertNull( exoskeleton_add_rules( $args ) ); + $ruleset = $this->validRuleProvider(); + unset( $ruleset[0][1]['lockout'] ); + $this->assertNull( exoskeleton_add_rules( $ruleset[0] ) ); $instance = Exoskeleton::get_instance(); $this->assertEquals( 2, count( $instance->rules ) ); } @@ -131,7 +126,7 @@ function test_adding_multiple_rules_including_invalid() { * @dataProvider validRuleProvider * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_custom_route_rule($rule) { + function test_add_valid_custom_route_rule( $rule ) { $rule['route'] = '/custom/route/that/does/not/exist'; $this->assertTrue( exoskeleton_add_rule( $rule ) ); @@ -139,15 +134,59 @@ function test_add_valid_custom_route_rule($rule) { /** * Check that exoskeleton will not overwrite an already existing rule + * * @dataProvider validRuleProvider * @param array $rule Valid exoskeleton rule definition. */ - function test_internal_add_rule_method_fails_when_adding_existing_rule($rule) { + function test_internal_add_rule_method_fails_when_adding_existing_rule( $rule ) { $exoskeleton = Exoskeleton::get_instance(); $this->assertTrue( exoskeleton_add_rule( $rule ) ); $this->assertFalse( $exoskeleton->add_rule( $rule ) ); } + /** + * Check process_custom_routes picks up pre-registered routes with exoskeleton rules. + */ + function test_process_custom_route_adds_rules() { + global $wp_rest_server; + + add_action( 'rest_api_init', function () { + register_rest_route( 'exoskeleton/v1', '/testing/(?P\d+)', array( + 'callback' => [ $this, 'custom_route_callback' ], + 'exoskeleton' => [ + 'window' => 10, + 'limit' => 5, + 'lockout' => 20, + ], + ) ); + } ); + + $exoskeleton = Exoskeleton::get_instance(); + $this->assertEquals( array(), $exoskeleton->rules ); + + // Remove action to prevent method being called automatically. + remove_action( 'rest_api_init', [ $exoskeleton, 'process_custom_routes' ], 999, 1 ); + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/testing/1' ) ); + + // Explicitly call method. + $exoskeleton->process_custom_routes( $wp_rest_server ); + add_action( 'rest_api_init', [ $exoskeleton, 'process_custom_routes' ], 999, 1 ); + + $this->assertNotEquals( array(), $exoskeleton->rules ); + $this->assertEquals( 1, count( $exoskeleton->rules ) ); + $this->assertEquals( '9afa7edee56d7fe5ce41c557fc4f812d_GET', key( $exoskeleton->rules ) ); + + } + + /** + * Simple method to provide a callback for custom routes during testing + * + * @param mixed $data data passed into the request for this route. + * @return mixed returns the data that was input to provide a valid output. + */ + public function custom_route_callback( $data ) { + return new WP_REST_Response( $data, 200 ); + } /** * Provides required fields for exoskeleton_add_rule @@ -197,6 +236,35 @@ public function validRuleProvider() { ]; } + /** + * Provides multiple valid exoskeleton rule definitions in single test + * + * @return array Valid exoskeleton rule methods + */ + public function multipleValidRuleProvider() { + return [ + [ + 'route' => '/wp/v2/posts', + 'window' => 10, + 'limit' => 25, + 'lockout' => 200, + 'method' => 'any', + ],[ + 'route' => '/wp/v2/post/1', + 'window' => 100, + 'limit' => 5, + 'lockout' => 60, + 'method' => 'GET', + ],[ + 'route' => '/wp/v2/post/2', + 'window' => 90, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'GET', + ], + ]; + } + /** * Get a single valid rule definition from the validRuleProvider * From 9a94812374134fd7c698638ba9fd5efe316d315d Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Fri, 26 May 2017 22:10:36 +0100 Subject: [PATCH 17/20] add test for limits applied on rule with multiple methods --- tests/test-exoskeleton-rest-api-calls.php | 46 ++++++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index bf8bf27..e672038 100755 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -50,7 +50,7 @@ public function test_key_generation( $rule, $test ) { * @return mixed returns the data that was input to provide a valid output. */ public function custom_route_callback( $data ) { - return $data; + return new WP_REST_Response($data, 200); } @@ -58,26 +58,29 @@ public function custom_route_callback( $data ) { * Check limits are honoured if a route is registered with exoskeleton limits. */ function test_pre_registered_custom_route_rules() { - + global $wp_rest_server; add_action( 'rest_api_init', function () { - register_rest_route( 'exoskeleton/v1', '/test/(?P\d+)', array( + register_rest_route( 'exoskeleton/v1', '/test_pre_registered_custom_route_rules/(?P\d+)', array( 'methods' => 'GET', 'callback' => [ $this, 'custom_route_callback' ], 'exoskeleton' => [ 'window' => 10, + 'method' => 'GET', 'limit' => 5, 'lockout' => 20, ], - ) ); + ), true ); } ); + do_action( 'rest_api_init', $wp_rest_server ); + for ( $request = 1; $request <= 6; ++$request ) { if ( $request < 5 ) { - $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test_pre_registered_custom_route_rules/1' ) ); $this->assertEquals( 200, $response->status ); } else { $this->expectException( Exception::class ); $this->expectExceptionMessage( 'locked' ); - $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test/1' ) ); + $response = rest_do_request( new WP_REST_Request( 'GET', '/exoskeleton/v1/test_pre_registered_custom_route_rules/1' ) ); } } } @@ -183,6 +186,37 @@ public function test_limits_apply_across_methods_for_any( $rule, $test ) { } + /** + * Limits should apply across methods if multiple methods are specified + * + * @dataProvider limitTestingProvider + * @param array $rule exoskeleton rule array. + * @param array $test extra test data. + */ + public function test_limits_apply_across_methods_for_multi_method_rules( $rule, $test ) { + // make sure the created rule applies to any method + $rule['method'] = 'HEAD,GET'; + $rule['treat_head_as_get'] = false; + $test['method'] = 'GET'; + $this->assertTrue( exoskeleton_add_rule( $rule ) ); + for ( $request = 1; $request <= $rule['limit'] + 1; ++$request ) { + if ( $request < ( $rule['limit'] - 1 ) ) { + // Initially test with one method + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } elseif ( $request == ( $rule['limit'] - 1 ) ) { + // Test second method allowed + $test['method'] = 'HEAD'; + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + $this->assertEquals( 200, $response->status ); + } else { + // Now call over the limit and ensure it is limited + $this->expectException( Exception::class ); + $this->expectExceptionMessage( 'locked' ); + $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); + } + } + } /** * Data provider From c3738e4d8694b0202be68b0be311f9ffce61ae91 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Tue, 30 May 2017 16:43:30 +0100 Subject: [PATCH 18/20] Added a space to prevent coding standard error in the line added to prevent dying during testing --- classes/class-exoskeleton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/class-exoskeleton.php b/classes/class-exoskeleton.php index 98d472a..68756a0 100755 --- a/classes/class-exoskeleton.php +++ b/classes/class-exoskeleton.php @@ -295,7 +295,7 @@ private function back_off_please( $rule_id, $matched_rule, $lock ) { // Do not die if testing. if ( defined( 'PHPUNIT_EXOSKELETON_TESTING' ) ) { - throw new Exception('locked'); + throw new Exception( 'locked' ); } die(); From 67013c4e62954cb98c532f797cbc81e7cb7ea9c2 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Tue, 30 May 2017 16:45:03 +0100 Subject: [PATCH 19/20] Fix coding standards errors --- tests/test-exoskeleton-rest-api-calls.php | 4 ++-- tests/test-exoskeleton-rule_validation.php | 24 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index e672038..07dc7fd 100755 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -50,7 +50,7 @@ public function test_key_generation( $rule, $test ) { * @return mixed returns the data that was input to provide a valid output. */ public function custom_route_callback( $data ) { - return new WP_REST_Response($data, 200); + return new WP_REST_Response( $data, 200 ); } @@ -204,7 +204,7 @@ public function test_limits_apply_across_methods_for_multi_method_rules( $rule, // Initially test with one method $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); $this->assertEquals( 200, $response->status ); - } elseif ( $request == ( $rule['limit'] - 1 ) ) { + } elseif ( ( $rule['limit'] - 1 ) === $request ) { // Test second method allowed $test['method'] = 'HEAD'; $response = rest_do_request( new WP_REST_Request( $test['method'], $rule['route'] ) ); diff --git a/tests/test-exoskeleton-rule_validation.php b/tests/test-exoskeleton-rule_validation.php index 2aed913..0eadfbc 100755 --- a/tests/test-exoskeleton-rule_validation.php +++ b/tests/test-exoskeleton-rule_validation.php @@ -126,22 +126,34 @@ public function requiredFieldsProvider() { public function invalidFieldValueProvider() { return [ [ - [ 'window' => 'string', ], + [ + 'window' => 'string', + ], ], [ - [ 'window' => -1, ], + [ + 'window' => -1, + ], ], [ - [ 'limit' => 'string', ], + [ + 'limit' => 'string', + ], ], [ - [ 'limit' => -1, ], + [ + 'limit' => -1, + ], ], [ - [ 'lockout' => 'string', ], + [ + 'lockout' => 'string', + ], ], [ - [ 'treat_head_like_get' => 'string', ], + [ + 'treat_head_like_get' => 'string', + ], ], ]; } From f3e2790a71bc8756110e4c1b6a95cbd574292771 Mon Sep 17 00:00:00 2001 From: Sarah Pantry Date: Wed, 31 May 2017 16:09:22 +0100 Subject: [PATCH 20/20] Refactor to remove unnecesary data providers and move cleanup to the teardown --- tests/test-exoskeleton-add-rule.php | 62 ++++------------------ tests/test-exoskeleton-rest-api-calls.php | 14 +++-- tests/test-exoskeleton-rule_validation.php | 57 +++++++------------- 3 files changed, 40 insertions(+), 93 deletions(-) diff --git a/tests/test-exoskeleton-add-rule.php b/tests/test-exoskeleton-add-rule.php index a013cad..dd82991 100755 --- a/tests/test-exoskeleton-add-rule.php +++ b/tests/test-exoskeleton-add-rule.php @@ -10,50 +10,14 @@ */ class ExoskeletonAddRuleTest extends WP_UnitTestCase { - - /** - * Three rule definitions - * - * @var array Three valid rule definitions - */ - protected static $three_valid_rules; - - /** - * Define some valid rule sets to reduce duplication in tests. - */ - static function setUpBeforeClass() { - parent::setUpBeforeClass(); - - self::$three_valid_rules = array( - [ - 'route' => '/wp/v2/posts', - 'window' => 10, - 'limit' => 25, - 'lockout' => 200, - 'method' => 'any', - ],[ - 'route' => '/wp/v2/post/1', - 'window' => 100, - 'limit' => 5, - 'lockout' => 60, - 'method' => 'GET', - ],[ - 'route' => '/wp/v2/post/2', - 'window' => 90, - 'limit' => 2, - 'lockout' => 30, - 'method' => 'GET', - ], - ); - } - /** - * Pre test setup + * Post test cleanup */ - function setUp() { + function tearDown() { + parent::tearDown(); + // Clear up any added rules $instance = Exoskeleton::get_instance(); $instance->rules = []; - } /** @@ -70,17 +34,14 @@ function test_get_instance() { * @param array $rule Valid exoskeleton rule definition. */ function test_add_valid_rule( $rule ) { - $this->assertTrue( exoskeleton_add_rule( $rule ) ); } /** * Test adding a valid rule with multiple methods - * - * @dataProvider validRuleProvider - * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_rule_with_multiple_methods( $rule ) { + function test_add_valid_rule_with_multiple_methods() { + $rule = $this->getValidRuleHelper(); $rule['method'] = 'POST,GET'; $this->assertTrue( exoskeleton_add_rule( $rule ) ); } @@ -123,10 +84,9 @@ function test_adding_multiple_rules_including_invalid() { * Test adding a valid rule for a custom route * Exoskeleton makes no check for existence of custom route. * - * @dataProvider validRuleProvider - * @param array $rule Valid exoskeleton rule definition. */ - function test_add_valid_custom_route_rule( $rule ) { + function test_add_valid_custom_route_rule() { + $rule = $this->getValidRuleHelper(); $rule['route'] = '/custom/route/that/does/not/exist'; $this->assertTrue( exoskeleton_add_rule( $rule ) ); @@ -134,11 +94,9 @@ function test_add_valid_custom_route_rule( $rule ) { /** * Check that exoskeleton will not overwrite an already existing rule - * - * @dataProvider validRuleProvider - * @param array $rule Valid exoskeleton rule definition. */ - function test_internal_add_rule_method_fails_when_adding_existing_rule( $rule ) { + function test_internal_add_rule_method_fails_when_adding_existing_rule() { + $rule = $this->getValidRuleHelper(); $exoskeleton = Exoskeleton::get_instance(); $this->assertTrue( exoskeleton_add_rule( $rule ) ); $this->assertFalse( $exoskeleton->add_rule( $rule ) ); diff --git a/tests/test-exoskeleton-rest-api-calls.php b/tests/test-exoskeleton-rest-api-calls.php index 07dc7fd..f5e5c9b 100755 --- a/tests/test-exoskeleton-rest-api-calls.php +++ b/tests/test-exoskeleton-rest-api-calls.php @@ -17,10 +17,6 @@ class ExoskeletonRestApiCallsTest extends WP_UnitTestCase { function setUp() { parent::setUp(); - // Clear existing rules. - $instance = Exoskeleton::get_instance(); - $instance->rules = []; - add_action( 'rest_api_init', function () { register_rest_route( 'exoskeleton/v1', '/exoskeleton/(?P\d+)', array( 'methods' => 'GET', @@ -30,6 +26,16 @@ function setUp() { } + /** + * Post test cleanup + */ + function tearDown() { + parent::tearDown(); + // Clear up any added rules + $instance = Exoskeleton::get_instance(); + $instance->rules = []; + } + /** * Test key generation * diff --git a/tests/test-exoskeleton-rule_validation.php b/tests/test-exoskeleton-rule_validation.php index 0eadfbc..524da10 100755 --- a/tests/test-exoskeleton-rule_validation.php +++ b/tests/test-exoskeleton-rule_validation.php @@ -11,36 +11,32 @@ class ExoskeletonRuleValidationTest extends WP_UnitTestCase { /** - * Pre test setup + * Post test cleanup */ - function setUp() { - parent::setUp(); - // Clear existing rules. - $instance = Exoskeleton::get_instance(); - $instance->rules = []; + function tearDown() { + parent::tearDown(); + // Clear up any added rules + $instance = Exoskeleton::get_instance(); + $instance->rules = []; } /** - * Check that exoskeleton validates rules with an invalid rule - * - * @dataProvider validRuleProvider - * @param array $rule Valid exoskeleton rule definition. + * Check that exoskeleton fails to validate an invalid rule */ - function test_validate_rule_fails_when_adding_invalid_rule( $rule ) { + function test_validate_rule_fails_when_adding_invalid_rule() { $exoskeleton = Exoskeleton::get_instance(); + $rule = $this->getValidRuleHelper(); unset( $rule['method'] ); $this->assertFalse( $exoskeleton->validate_rule( $rule ) ); } /** * Check that exoskeleton validates rules with valid rule - * - * @dataProvider validRuleProvider - * @param array $rule Valid exoskeleton rule definition. */ - function test_validate_rule_passes_when_adding_a_valid_rule( $rule ) { + function test_validate_rule_passes_when_adding_a_valid_rule() { $exoskeleton = Exoskeleton::get_instance(); + $rule = $this->getValidRuleHelper(); $this->assertTrue( $exoskeleton->validate_rule( $rule ) ); } @@ -158,34 +154,21 @@ public function invalidFieldValueProvider() { ]; } - /** - * Data provider - * - * @return array Valid exoskeleton rule methods - */ - public function validRuleProvider() { - return [ - [ - [ - 'route' => '/wp/v2/posts', - 'window' => 5, - 'limit' => 2, - 'lockout' => 30, - 'method' => 'any', - 'treat_head_like_get' => false, - ], - ], - ]; - } /** - * Get a single valid rule definition from the validRuleProvider + * Get a single valid rule definition * * @return array Valid Exoskeleton rule definition */ public function getValidRuleHelper() { - $rules = $this->validRuleProvider(); - return $rules[0][0]; + return [ + 'route' => '/wp/v2/posts', + 'window' => 5, + 'limit' => 2, + 'lockout' => 30, + 'method' => 'any', + 'treat_head_like_get' => false, + ]; }