From 8961f8006bbe603ada162f0e8b7f431645e28f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Perona?= Date: Fri, 14 Aug 2026 14:28:11 -0400 Subject: [PATCH 1/3] fix: remove WP Rocket coupling (rocket_get_constant) from generic package ApiTrait::getApiCredential() now resolves constants with a neutral defined()/constant() check instead of calling WP Rocket's rocket_get_constant() helper, and the rocket_get_constant()/ rocket_has_constant() polyfills are removed from Fixtures/polyfills.php so consumers of this generic package no longer inherit a rocket_-prefixed coupling. Closes #35 Co-Authored-By: Claude Sonnet 5 --- README.md | 2 +- Tests/Unit/testApiTrait.php | 83 ++++++++++++++++++++++++++++++++++++ phpstan-baseline.neon | 12 ------ src/Fixtures/polyfills.php | 40 +++++------------ src/Integration/ApiTrait.php | 6 ++- 5 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 Tests/Unit/testApiTrait.php diff --git a/README.md b/README.md index 5e6d141..1c82951 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This reusable package bootstraps our PHPUnit unit and integration tests. It incl - bootstrapping for both Unit and Integration tests - `phpunit.xml.dist` for each test suite - `TestCase` for each test suite -- Common polyfill functions +- An extension point (`TestCaseTrait::stubPolyfills()`) for stubbing your own project-specific polyfill functions This means your repo only needs its tests. w00t! diff --git a/Tests/Unit/testApiTrait.php b/Tests/Unit/testApiTrait.php new file mode 100644 index 0000000..dac6f04 --- /dev/null +++ b/Tests/Unit/testApiTrait.php @@ -0,0 +1,83 @@ +tmp_dir = sys_get_temp_dir() . '/wpmedia-phpunit-apitrait-' . uniqid(); + + mkdir( $this->tmp_dir ); + } + + protected function tear_down() { + array_map( 'unlink', glob( "{$this->tmp_dir}/*.php" ) ); + rmdir( $this->tmp_dir ); + + parent::tear_down(); + } + + public function testShouldReturnEnvironmentVariableWhenSet() { + putenv( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL=from_env' ); + + $this->assertSame( 'from_env', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL' ) ); + + putenv( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL' ); + } + + public function testShouldReturnEmptyStringWhenNoConfigFileIsSet() { + $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT' ) ); + } + + public function testShouldReturnEmptyStringWhenConfigFileIsNotReadable() { + ApiTraitTestDouble::setConfigFile( $this->tmp_dir . '/', 'missing-credentials.php' ); + + $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT' ) ); + } + + public function testShouldReturnConstantValueDefinedInConfigFile() { + file_put_contents( $this->tmp_dir . '/credentials.php', "tmp_dir . '/', 'credentials.php' ); + + $this->assertSame( 'from_constant', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_CONSTANT' ) ); + } + + public function testShouldReturnEmptyStringWhenConstantIsNotDefinedInConfigFile() { + file_put_contents( $this->tmp_dir . '/empty-credentials.php', 'tmp_dir . '/', 'empty-credentials.php' ); + + $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNDEFINED_CONSTANT' ) ); + } +} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2a37334..89297ff 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -54,12 +54,6 @@ parameters: count: 1 path: src/Integration/AdminTestCase.php - - - rawMessage: 'Method WPMedia\PHPUnit\Integration\AjaxTestCase::getApiCredential() should return string but returns bool.' - identifier: return.type - count: 1 - path: src/Integration/AjaxTestCase.php - - rawMessage: 'Class WP_REST_Request referenced with incorrect case: WP_Rest_Request.' identifier: class.nameCase @@ -84,12 +78,6 @@ parameters: count: 1 path: src/Integration/RESTfulTestCase.php - - - rawMessage: 'Method WPMedia\PHPUnit\Integration\RESTfulTestCase::getApiCredential() should return string but returns bool.' - identifier: return.type - count: 1 - path: src/Integration/RESTfulTestCase.php - - rawMessage: Variable $wp_rest_server in PHPDoc tag @var does not exist. identifier: varTag.variableNotFound diff --git a/src/Fixtures/polyfills.php b/src/Fixtures/polyfills.php index 94618a1..17bcd47 100644 --- a/src/Fixtures/polyfills.php +++ b/src/Fixtures/polyfills.php @@ -1,32 +1,12 @@ Date: Fri, 14 Aug 2026 14:49:37 -0400 Subject: [PATCH 2/3] refactor: drop stubPolyfills() extension point and bring ApiTrait test suite to standard Follow-up to the #35 WP Rocket decoupling: removes src/Fixtures/polyfills.php entirely along with TestCaseTrait::stubPolyfills() and Unit\TestCase's $stubPolyfills gate, since nothing in the package uses that extension point anymore. Adds declare(strict_types=1) to every file touched by this change, brings ApiTrait.php, TestCaseTrait.php, and Unit\TestCase.php into PHPCS scope (0 violations), and rewrites the ApiTrait unit test as a single @dataProvider-driven test following the getDefaultVfs() convention, with its scenarios in Tests/Fixtures/ApiTrait/getApiCredential.php. Refs #35 Co-Authored-By: Claude Sonnet 5 --- README.md | 1 - Tests/Fixtures/ApiTrait/getApiCredential.php | 50 +++++++++++ Tests/Unit/ApiTrait/ApiTraitTestDouble.php | 56 ++++++++++++ Tests/Unit/ApiTrait/getApiCredential.php | 93 ++++++++++++++++++++ Tests/Unit/testApiTrait.php | 83 ----------------- phpcs.xml.dist | 8 +- src/Fixtures/polyfills.php | 12 --- src/Integration/ApiTrait.php | 19 +++- src/TestCaseTrait.php | 34 ++++--- src/Unit/TestCase.php | 20 +---- 10 files changed, 246 insertions(+), 130 deletions(-) create mode 100644 Tests/Fixtures/ApiTrait/getApiCredential.php create mode 100644 Tests/Unit/ApiTrait/ApiTraitTestDouble.php create mode 100644 Tests/Unit/ApiTrait/getApiCredential.php delete mode 100644 Tests/Unit/testApiTrait.php delete mode 100644 src/Fixtures/polyfills.php diff --git a/README.md b/README.md index 1c82951..7549a64 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ This reusable package bootstraps our PHPUnit unit and integration tests. It incl - bootstrapping for both Unit and Integration tests - `phpunit.xml.dist` for each test suite - `TestCase` for each test suite -- An extension point (`TestCaseTrait::stubPolyfills()`) for stubbing your own project-specific polyfill functions This means your repo only needs its tests. w00t! diff --git a/Tests/Fixtures/ApiTrait/getApiCredential.php b/Tests/Fixtures/ApiTrait/getApiCredential.php new file mode 100644 index 0000000..ab67453 --- /dev/null +++ b/Tests/Fixtures/ApiTrait/getApiCredential.php @@ -0,0 +1,50 @@ + [ + 'env_name' => 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL', + 'env_value' => 'from_env', + 'config_filename' => null, + 'config_file_contents' => null, + 'credential_name' => 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL', + 'expected' => 'from_env', + ], + + 'no config file is set' => [ + 'env_name' => null, + 'env_value' => null, + 'config_filename' => null, + 'config_file_contents' => null, + 'credential_name' => 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT', + 'expected' => '', + ], + + 'config file is not readable' => [ + 'env_name' => null, + 'env_value' => null, + 'config_filename' => 'missing-credentials.php', + 'config_file_contents' => null, + 'credential_name' => 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT', + 'expected' => '', + ], + + 'constant is defined in the config file' => [ + 'env_name' => null, + 'env_value' => null, + 'config_filename' => 'credentials.php', + 'config_file_contents' => " 'WPMEDIA_PHPUNIT_TEST_CONSTANT', + 'expected' => 'from_constant', + ], + + 'constant is not defined in the config file' => [ + 'env_name' => null, + 'env_value' => null, + 'config_filename' => 'empty-credentials.php', + 'config_file_contents' => ' 'WPMEDIA_PHPUNIT_TEST_UNDEFINED_CONSTANT', + 'expected' => '', + ], +]; diff --git a/Tests/Unit/ApiTrait/ApiTraitTestDouble.php b/Tests/Unit/ApiTrait/ApiTraitTestDouble.php new file mode 100644 index 0000000..9f75d6b --- /dev/null +++ b/Tests/Unit/ApiTrait/ApiTraitTestDouble.php @@ -0,0 +1,56 @@ +tmp_dir = sys_get_temp_dir() . '/wpmedia-phpunit-apitrait-' . uniqid(); + + mkdir( $this->tmp_dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- WP_Filesystem is not available in the Unit test suite; this creates a throwaway scratch directory. + } + + /** + * Removes the scratch directory after each scenario. + * + * @return void + */ + protected function tear_down() { + array_map( 'unlink', glob( "{$this->tmp_dir}/*.php" ) ); + rmdir( $this->tmp_dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem is not available in the Unit test suite; this removes the throwaway scratch directory. + + parent::tear_down(); + } + + /** + * Asserts that getApiCredential() returns the expected value for each scenario. + * + * @dataProvider getApiCredentialDataProvider + * + * @param string|null $env_name Name of the environment variable to set before running the scenario, if any. + * @param string|null $env_value Value to set the environment variable to, if any. + * @param string|null $config_filename Name of the config file to point the trait at, if any. + * @param string|null $config_file_contents Contents to write to the config file before running the scenario, if any. + * @param string $credential_name Name of the environment variable or constant to look up. + * @param string $expected Expected return value. + * + * @return void + */ + public function testShouldReturnTheExpectedCredential( $env_name, $env_value, $config_filename, $config_file_contents, $credential_name, $expected ) { + if ( null !== $env_name ) { + putenv( "{$env_name}={$env_value}" ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- Test-only: simulates the environment variable that getApiCredential() reads via getenv(). + } + + if ( null !== $config_filename ) { + if ( null !== $config_file_contents ) { + file_put_contents( $this->tmp_dir . '/' . $config_filename, $config_file_contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- WP_Filesystem is not available in the Unit test suite; this writes the throwaway config file fixture. + } + + ApiTraitTestDouble::set_config_file( $this->tmp_dir . '/', $config_filename ); + } + + $this->assertSame( $expected, ApiTraitTestDouble::get_credential( $credential_name ) ); + + if ( null !== $env_name ) { + putenv( $env_name ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- Test-only: unsets the environment variable set above. + } + } + + /** + * Provides the scenarios from the Fixtures directory. + * + * @return array test data. + */ + public function getApiCredentialDataProvider() { + return $this->getTestData( __DIR__, 'getApiCredential' ); + } +} diff --git a/Tests/Unit/testApiTrait.php b/Tests/Unit/testApiTrait.php deleted file mode 100644 index dac6f04..0000000 --- a/Tests/Unit/testApiTrait.php +++ /dev/null @@ -1,83 +0,0 @@ -tmp_dir = sys_get_temp_dir() . '/wpmedia-phpunit-apitrait-' . uniqid(); - - mkdir( $this->tmp_dir ); - } - - protected function tear_down() { - array_map( 'unlink', glob( "{$this->tmp_dir}/*.php" ) ); - rmdir( $this->tmp_dir ); - - parent::tear_down(); - } - - public function testShouldReturnEnvironmentVariableWhenSet() { - putenv( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL=from_env' ); - - $this->assertSame( 'from_env', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL' ) ); - - putenv( 'WPMEDIA_PHPUNIT_TEST_CREDENTIAL' ); - } - - public function testShouldReturnEmptyStringWhenNoConfigFileIsSet() { - $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT' ) ); - } - - public function testShouldReturnEmptyStringWhenConfigFileIsNotReadable() { - ApiTraitTestDouble::setConfigFile( $this->tmp_dir . '/', 'missing-credentials.php' ); - - $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNSET_CONSTANT' ) ); - } - - public function testShouldReturnConstantValueDefinedInConfigFile() { - file_put_contents( $this->tmp_dir . '/credentials.php', "tmp_dir . '/', 'credentials.php' ); - - $this->assertSame( 'from_constant', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_CONSTANT' ) ); - } - - public function testShouldReturnEmptyStringWhenConstantIsNotDefinedInConfigFile() { - file_put_contents( $this->tmp_dir . '/empty-credentials.php', 'tmp_dir . '/', 'empty-credentials.php' ); - - $this->assertSame( '', ApiTraitTestDouble::getCredential( 'WPMEDIA_PHPUNIT_TEST_UNDEFINED_CONSTANT' ) ); - } -} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 26c2a16..70f138d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -11,11 +11,17 @@ opportunistically, and widen this to the full source tree (src/ plus Tests/) once the codebase is fully compliant. --> + src/Integration/ApiTrait.php src/Integration/HttpRequestTrait.php + src/TestCaseTrait.php + src/Unit/TestCase.php src/VirtualFilesystemTestTrait.php + Tests/Fixtures/ApiTrait/getApiCredential.php + Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php + Tests/Unit/ApiTrait/ApiTraitTestDouble.php + Tests/Unit/ApiTrait/getApiCredential.php Tests/Unit/VirtualFilesystemDirect/TestCase.php Tests/Unit/VirtualFilesystemTestTrait/getDefaultVfs.php - Tests/Fixtures/VirtualFilesystemTestTrait/getDefaultVfs.php vendor/* diff --git a/src/Fixtures/polyfills.php b/src/Fixtures/polyfills.php deleted file mode 100644 index 17bcd47..0000000 --- a/src/Fixtures/polyfills.php +++ /dev/null @@ -1,12 +0,0 @@ -getProperty( $property ); self::set_reflector_accessible( $property, true ); @@ -96,8 +94,18 @@ protected function set_reflective_property( $value, $property, $instance ) { return $property; } - protected function getNonPublicPropertyValue( $property, $class, $instance = null ) { - $property = $this->get_reflective_property( $property, $class ); + /** + * Gets the value of a private/protected property. + * + * @param string $property Property name for which to gain access. + * @param string|mixed $class_name Class name or instance. + * @param mixed|null $instance Instance of the target object, if the property is not static. + * + * @return mixed the property's value. + * @throws ReflectionException Throws an exception if property does not exist. + */ + protected function getNonPublicPropertyValue( $property, $class_name, $instance = null ) { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Public API method; renaming would be a breaking change for consumers. + $property = $this->get_reflective_property( $property, $class_name ); if ( is_null( $instance ) || $property->isStatic() ) { return $property->getValue(); diff --git a/src/Unit/TestCase.php b/src/Unit/TestCase.php index 86fd094..53c78bd 100644 --- a/src/Unit/TestCase.php +++ b/src/Unit/TestCase.php @@ -1,5 +1,7 @@ Date: Fri, 14 Aug 2026 15:00:29 -0400 Subject: [PATCH 3/3] fix: remove deleted src/Fixtures path from PHPStan config The src/Fixtures directory was removed along with the empty polyfills file; PHPStan CI failed because the path no longer exists. Refs #35 Co-Authored-By: Claude Opus 4.8 --- phpstan.neon.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2080dd3..c376acc 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -13,7 +13,6 @@ parameters: - src/bootstrap-functions.php - src/Integration/ - src/Unit/ - - src/Fixtures/ - Tests/ scanFiles: - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php