From a27f3395b7b063bf82e87c9fc4d753e1aacf8018 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Sun, 17 May 2026 19:40:16 +0300 Subject: [PATCH 1/6] docs: another reference --- docs/guides/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/testing.md b/docs/guides/testing.md index e230e0c0..feac3e87 100644 --- a/docs/guides/testing.md +++ b/docs/guides/testing.md @@ -28,7 +28,7 @@ First, ensure the wp-env environment is running: npm run wp-env:test start ``` -This starts a WordPress instance at http://localhost:8888 with all required dependencies. +This starts a WordPress instance at http://localhost:8889 with all required dependencies. ### Running All Tests From 56fd0dbfa9dfe298c534c106e2e4c46ded6df1a9 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Sun, 17 May 2026 21:38:45 +0300 Subject: [PATCH 2/6] tests: relocate to `tests/phpunit` --- .wp-env.test.json | 2 +- composer.json | 2 +- docs/guides/testing.md | 18 +-- phpunit.xml.dist | 6 +- tests/Unit/PluginTest.php | 37 ------ tests/{ => phpunit}/Fixtures/DummyAbility.php | 113 ------------------ .../Fixtures/DummyErrorHandler.php | 0 .../Fixtures/DummyObservabilityHandler.php | 0 .../{ => phpunit}/Fixtures/DummyTransport.php | 0 .../BuilderPromptExecutionTest.php | 0 .../ErrorHandlingIntegrationTest.php | 0 .../Integration/HttpTransportTest.php | 0 .../RegistrationValidationTest.php | 0 .../Integration/TransportRoutingTest.php | 0 .../Integration/WordPressFiltersTest.php | 0 tests/{ => phpunit}/Stubs/WpCliClasses.php | 0 tests/{ => phpunit}/Stubs/WpCliStubs.php | 0 tests/{ => phpunit}/Stubs/WpCliUtils.php | 0 tests/{ => phpunit}/TestCase.php | 110 +---------------- .../DiscoverAbilitiesAbilityTest.php | 0 .../Abilities/ExecuteAbilityAbilityTest.php | 0 .../Abilities/GetAbilityInfoAbilityTest.php | 0 .../{ => phpunit}/Unit/Cli/McpCommandTest.php | 0 .../Unit/Cli/StdioServerBridgeTest.php | 0 .../Unit/Core/DeveloperErrorsTest.php | 58 +-------- .../Unit/Core/McpAdapterConfigTest.php | 6 +- .../Unit/Core/McpAdapterErrorHandlingTest.php | 0 .../Unit/Core/McpComponentRegistryTest.php | 0 .../Unit/Core/McpTransportFactoryTest.php | 22 ++-- .../Unit/Core/McpVersionNegotiatorTest.php | 0 .../Domain/Prompts/McpPromptValidatorTest.php | 0 .../Resources/McpResourceValidatorTest.php | 0 .../Domain/Tools/McpToolValidatorTest.php | 0 .../Utils/AbilityArgumentNormalizerTest.php | 0 .../Domain/Utils/ContentBlockHelperTest.php | 0 .../Domain/Utils/McpAnnotationMapperTest.php | 0 .../Domain/Utils/McpNameSanitizerTest.php | 0 .../Unit/Domain/Utils/McpValidatorTest.php | 0 .../Domain/Utils/SchemaTransformerTest.php | 0 .../Unit/ErrorHandlers/ErrorEnvelopeTest.php | 0 .../McpErrorHandlerInterfaceTest.php | 0 .../ErrorResponseConsistencyTest.php | 0 .../Unit/Handlers/HandlerHelperTraitTest.php | 0 .../Unit/Handlers/InitializeHandlerTest.php | 0 .../Unit/Handlers/PromptsHandlerTest.php | 0 .../Handlers/ResourcesHandlerListTest.php | 0 .../Handlers/ResourcesHandlerReadTest.php | 0 .../Unit/Handlers/ResourcesHandlerTest.php | 0 .../Unit/Handlers/SystemHandlerTest.php | 0 .../Unit/Handlers/ToolsHandlerCallTest.php | 0 .../Unit/Handlers/ToolsHandlerListTest.php | 0 .../Unit/Handlers/ToolsHandlerTest.php | 0 .../ErrorLogMcpErrorHandlerTest.php | 0 .../ErrorHandling/McpErrorFactoryTest.php | 0 .../ErrorHandling/NullMcpErrorHandlerTest.php | 0 .../ErrorLogMcpObservabilityHandlerTest.php | 0 .../Observability/FailureReasonTest.php | 0 .../McpObservabilityHelperTraitTest.php | 0 tests/{ => phpunit}/Unit/McpServerTest.php | 0 tests/{ => phpunit}/Unit/McpTransportTest.php | 0 .../Unit/Observability/NullHandlerTest.php | 0 tests/phpunit/Unit/PluginTest.php | 28 +++++ .../Unit/Prompts/McpPromptBuilderTest.php | 0 .../Unit/Prompts/McpPromptTest.php | 0 .../RegisterAbilityAsMcpPromptTest.php | 0 .../Unit/Resources/McpResourceTest.php | 0 .../RegisterAbilityAsMcpResourceTest.php | 4 +- .../Unit/Servers/DefaultServerFactoryTest.php | 0 .../{ => phpunit}/Unit/Tools/McpToolTest.php | 0 .../Tools/RegisterAbilityAsMcpToolTest.php | 0 .../DtoSerializationRegressionTest.php | 0 .../Infrastructure/HttpRequestHandlerTest.php | 0 .../HttpSessionValidatorTest.php | 0 .../JsonRpcResponseBuilderTest.php | 0 .../McpTransportContextTest.php | 0 .../Infrastructure/RequestRouterTest.php | 0 .../Unit/Transport/McpSessionManagerTest.php | 0 tests/{ => phpunit}/bootstrap.php | 10 +- 78 files changed, 67 insertions(+), 349 deletions(-) delete mode 100644 tests/Unit/PluginTest.php rename tests/{ => phpunit}/Fixtures/DummyAbility.php (92%) rename tests/{ => phpunit}/Fixtures/DummyErrorHandler.php (100%) rename tests/{ => phpunit}/Fixtures/DummyObservabilityHandler.php (100%) rename tests/{ => phpunit}/Fixtures/DummyTransport.php (100%) rename tests/{ => phpunit}/Integration/BuilderPromptExecutionTest.php (100%) rename tests/{ => phpunit}/Integration/ErrorHandlingIntegrationTest.php (100%) rename tests/{ => phpunit}/Integration/HttpTransportTest.php (100%) rename tests/{ => phpunit}/Integration/RegistrationValidationTest.php (100%) rename tests/{ => phpunit}/Integration/TransportRoutingTest.php (100%) rename tests/{ => phpunit}/Integration/WordPressFiltersTest.php (100%) rename tests/{ => phpunit}/Stubs/WpCliClasses.php (100%) rename tests/{ => phpunit}/Stubs/WpCliStubs.php (100%) rename tests/{ => phpunit}/Stubs/WpCliUtils.php (100%) rename tests/{ => phpunit}/TestCase.php (56%) rename tests/{ => phpunit}/Unit/Abilities/DiscoverAbilitiesAbilityTest.php (100%) rename tests/{ => phpunit}/Unit/Abilities/ExecuteAbilityAbilityTest.php (100%) rename tests/{ => phpunit}/Unit/Abilities/GetAbilityInfoAbilityTest.php (100%) rename tests/{ => phpunit}/Unit/Cli/McpCommandTest.php (100%) rename tests/{ => phpunit}/Unit/Cli/StdioServerBridgeTest.php (100%) rename tests/{ => phpunit}/Unit/Core/DeveloperErrorsTest.php (70%) rename tests/{ => phpunit}/Unit/Core/McpAdapterConfigTest.php (98%) rename tests/{ => phpunit}/Unit/Core/McpAdapterErrorHandlingTest.php (100%) rename tests/{ => phpunit}/Unit/Core/McpComponentRegistryTest.php (100%) rename tests/{ => phpunit}/Unit/Core/McpTransportFactoryTest.php (89%) rename tests/{ => phpunit}/Unit/Core/McpVersionNegotiatorTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Prompts/McpPromptValidatorTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Resources/McpResourceValidatorTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Tools/McpToolValidatorTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/AbilityArgumentNormalizerTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/ContentBlockHelperTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/McpAnnotationMapperTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/McpNameSanitizerTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/McpValidatorTest.php (100%) rename tests/{ => phpunit}/Unit/Domain/Utils/SchemaTransformerTest.php (100%) rename tests/{ => phpunit}/Unit/ErrorHandlers/ErrorEnvelopeTest.php (100%) rename tests/{ => phpunit}/Unit/ErrorHandlers/McpErrorHandlerInterfaceTest.php (100%) rename tests/{ => phpunit}/Unit/ErrorHandling/ErrorResponseConsistencyTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/HandlerHelperTraitTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/InitializeHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/PromptsHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ResourcesHandlerListTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ResourcesHandlerReadTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ResourcesHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/SystemHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ToolsHandlerCallTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ToolsHandlerListTest.php (100%) rename tests/{ => phpunit}/Unit/Handlers/ToolsHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/ErrorHandling/McpErrorFactoryTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/ErrorHandling/NullMcpErrorHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/Observability/ErrorLogMcpObservabilityHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/Observability/FailureReasonTest.php (100%) rename tests/{ => phpunit}/Unit/Infrastructure/Observability/McpObservabilityHelperTraitTest.php (100%) rename tests/{ => phpunit}/Unit/McpServerTest.php (100%) rename tests/{ => phpunit}/Unit/McpTransportTest.php (100%) rename tests/{ => phpunit}/Unit/Observability/NullHandlerTest.php (100%) create mode 100644 tests/phpunit/Unit/PluginTest.php rename tests/{ => phpunit}/Unit/Prompts/McpPromptBuilderTest.php (100%) rename tests/{ => phpunit}/Unit/Prompts/McpPromptTest.php (100%) rename tests/{ => phpunit}/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php (100%) rename tests/{ => phpunit}/Unit/Resources/McpResourceTest.php (100%) rename tests/{ => phpunit}/Unit/Resources/RegisterAbilityAsMcpResourceTest.php (98%) rename tests/{ => phpunit}/Unit/Servers/DefaultServerFactoryTest.php (100%) rename tests/{ => phpunit}/Unit/Tools/McpToolTest.php (100%) rename tests/{ => phpunit}/Unit/Tools/RegisterAbilityAsMcpToolTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/DtoSerializationRegressionTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/HttpRequestHandlerTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/HttpSessionValidatorTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/JsonRpcResponseBuilderTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/McpTransportContextTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/Infrastructure/RequestRouterTest.php (100%) rename tests/{ => phpunit}/Unit/Transport/McpSessionManagerTest.php (100%) rename tests/{ => phpunit}/bootstrap.php (88%) diff --git a/.wp-env.test.json b/.wp-env.test.json index fb6efddc..410d986b 100644 --- a/.wp-env.test.json +++ b/.wp-env.test.json @@ -3,7 +3,7 @@ "testsEnvironment": false, "port": 8889, "core": null, - "plugins": [ "WordPress/abilities-api", "./." ], + "plugins": [ "./." ], "config": { "FS_METHOD": "direct", "WP_DEBUG": true, diff --git a/composer.json b/composer.json index 31877362..29fb0b23 100644 --- a/composer.json +++ b/composer.json @@ -46,7 +46,7 @@ "autoload-dev": { "psr-4": { "WP\\MCP\\Tests\\": [ - "tests/" + "tests/phpunit/" ] } }, diff --git a/docs/guides/testing.md b/docs/guides/testing.md index feac3e87..833e46cf 100644 --- a/docs/guides/testing.md +++ b/docs/guides/testing.md @@ -12,9 +12,9 @@ See [CONTRIBUTING.md](../../CONTRIBUTING.md#prerequisites) for full setup requir ## Test Layout -- `tests/Unit/*`: fast unit tests for pure PHP logic and MCP handlers -- `tests/Integration/*`: WordPress-integration tests that exercise filters, permissions, routing, and transport layers -- `tests/Fixtures/*`: test doubles (dummy error/observability handlers, abilities, transport) +- `tests/phpunit/Unit/*`: fast unit tests for pure PHP logic and MCP handlers +- `tests/phpunit/Integration/*`: WordPress-integration tests that exercise filters, permissions, routing, and transport layers +- `tests/phpunit/Fixtures/*`: test doubles (dummy error/observability handlers, abilities, transport) ## Running Tests @@ -49,7 +49,7 @@ You can pass PHPUnit arguments to the test script using `--`: npm run test:php -- --filter test_execute_with_public_mcp_filtering # Run a specific test file -npm run test:php -- tests/Unit/Handlers/ToolsHandlerCallTest.php +npm run test:php -- tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php # Run tests matching a pattern npm run test:php -- --filter "Tools.*" @@ -75,12 +75,12 @@ Coverage reports will be generated: The test suite includes fixtures for verifying observability and error handling: -**DummyObservabilityHandler** (`tests/Fixtures/DummyObservabilityHandler.php`) +**DummyObservabilityHandler** (`tests/phpunit/Fixtures/DummyObservabilityHandler.php`) - Captures `record_event()` calls with event names, tags, and optional timing data - Stores events in `$events` array for test assertions - Used to verify that requests, successes, errors, and timings are properly tracked -**DummyErrorHandler** (`tests/Fixtures/DummyErrorHandler.php`) +**DummyErrorHandler** (`tests/phpunit/Fixtures/DummyErrorHandler.php`) - Captures `log()` calls with messages, context, and error types - Stores logs in `$logs` array for test assertions - Used to verify error handling and logging behavior @@ -89,9 +89,9 @@ Tests verify that error responses adhere to JSON-RPC 2.0 format: `{ jsonrpc, id, ## Writing New Tests -- Place unit tests under `tests/Unit/.../*Test.php` -- Place integration tests under `tests/Integration/.../*Test.php` -- Use fixtures in `tests/Fixtures` or create your own test doubles +- Place unit tests under `tests/phpunit/Unit/.../*Test.php` +- Place integration tests under `tests/phpunit/Integration/.../*Test.php` +- Use fixtures in `tests/phpunit/Fixtures` or create your own test doubles - Follow the Arrange-Act-Assert (AAA) pattern - Mock external dependencies using PHPUnit mocks - Test files should mirror the source structure with a `Test.php` suffix diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5c7b3b4e..779798cc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -2,7 +2,7 @@ - ./tests/Unit/ + ./tests/phpunit/Unit/ - ./tests/Integration/ + ./tests/phpunit/Integration/ diff --git a/tests/Unit/PluginTest.php b/tests/Unit/PluginTest.php deleted file mode 100644 index ef98b129..00000000 --- a/tests/Unit/PluginTest.php +++ /dev/null @@ -1,37 +0,0 @@ -assertNotEmpty( $this->doing_it_wrong_log, 'Expected _doing_it_wrong to be called when cloning plugin. Captured: ' . wp_json_encode( $this->doing_it_wrong_log ) ); - $this->assertDoingItWrongTriggered( '__clone', 'should not be cloned' ); - } - - public function test_plugin_wakeup_triggers_doing_it_wrong(): void { - $plugin = Plugin::instance(); - - // Attempt to unserialize the plugin - $serialized = serialize( $plugin ); - @unserialize( $serialized ); - - // Verify _doing_it_wrong was called - // __FUNCTION__ returns '__wakeup' not the full class name - $this->assertNotEmpty( $this->doing_it_wrong_log, 'Expected _doing_it_wrong to be called when unserializing plugin. Captured: ' . wp_json_encode( $this->doing_it_wrong_log ) ); - $this->assertDoingItWrongTriggered( '__wakeup', 'De-serializing' ); - } -} - diff --git a/tests/Fixtures/DummyAbility.php b/tests/phpunit/Fixtures/DummyAbility.php similarity index 92% rename from tests/Fixtures/DummyAbility.php rename to tests/phpunit/Fixtures/DummyAbility.php index 20378f11..4129575d 100644 --- a/tests/Fixtures/DummyAbility.php +++ b/tests/phpunit/Fixtures/DummyAbility.php @@ -24,38 +24,6 @@ public static function register_category(): void { ); } - /** - * Registers all dummy abilities for testing. - * - * Sets up action hooks to register category and abilities at the correct times: - * - Category registration during 'wp_abilities_api_categories_init' - * - Abilities registration during 'wp_abilities_api_init' - * - * Then fires the hooks if they haven't been fired yet. - * Does not check if abilities already exist - if they do, test isolation has failed. - * - * @return void - */ - public static function register_all(): void { - // Hook category registration to the proper action - add_action( 'wp_abilities_api_categories_init', array( self::class, 'register_category' ) ); - - // Fire categories init hook if not already fired - if ( ! did_action( 'wp_abilities_api_categories_init' ) ) { - do_action( 'wp_abilities_api_categories_init' ); - } - - // Hook abilities registration to the proper action - add_action( 'wp_abilities_api_init', array( self::class, 'register_abilities' ) ); - - // Fire abilities init hook if not already fired - if ( did_action( 'wp_abilities_api_init' ) ) { - return; - } - - do_action( 'wp_abilities_api_init' ); - } - /** * Registers all the dummy abilities. * @@ -1686,87 +1654,6 @@ public static function register_abilities(): void { ); } - /** - * Unregisters all dummy abilities and the test category. - * - * Also removes the action hooks to prevent duplicate registrations. - * Does not check if abilities/category exist - if they don't, test setup has failed. - * - * @return void - */ - public static function unregister_all(): void { - // Remove action hooks to prevent re-registration - remove_action( 'wp_abilities_api_categories_init', array( self::class, 'register_category' ) ); - remove_action( 'wp_abilities_api_init', array( self::class, 'register_abilities' ) ); - - // Unregister all abilities - $names = array( - 'test/always-allowed', - 'test/permission-denied', - 'test/permission-exception', - 'test/execute-exception', - 'test/image', - 'test/resource', - 'test/prompt', - 'test/annotated-ability', - 'test/null-annotations', - 'test/with-instructions', - 'test/mcp-native', - 'test/no-annotations', - 'test/all-null-annotations', - 'test/resource-with-annotations', - 'test/resource-partial-annotations', - 'test/resource-invalid-annotations', - 'test/prompt-with-annotations', - 'test/prompt-partial-annotations', - 'test/prompt-invalid-annotations', - 'test/prompt-flattened-string', - 'test/prompt-flattened-array', - 'test/prompt-with-titles', - 'test/prompt-mixed-required', - 'test/prompt-empty-object', - 'test/prompt-no-schema', - 'test/resource-whitespace-uri', - 'test/embedded-text-resource', - 'test/embedded-blob-resource', - 'test/meta-leak', - 'test/with-icons', - 'test/with-mixed-icons', - 'test/with-custom-meta', - 'test/with-icons-and-meta', - 'test/resource-new-meta', - 'test/resource-invalid-uri', - 'test/resource-invalid-mimetype', - 'test/resource-with-size', - 'test/resource-invalid-annotations-new-meta', - 'test/resource-mixed-annotations', - 'test/resource-with-icons', - 'test/resource-missing-uri', - 'test/resource-valid-mimetype', - 'test/resource-blob-content', - 'test/resource-multiple-contents', - 'test/resource-text-with-mimetype', - 'test/resource-plain-string', - 'test/prompt-explicit-args', - 'test/prompt-explicit-args-override', - 'test/prompt-empty-explicit-args', - 'test/prompt-invalid-explicit-args-no-name', - 'test/prompt-invalid-explicit-args-not-array', - 'test/prompt-explicit-args-all-fields', - 'test/prompt-with-icons', - 'test/prompt-with-mixed-icons', - 'test/prompt-with-custom-meta', - 'test/prompt-with-icons-and-meta', - ); - - foreach ( $names as $name ) { - wp_unregister_ability( $name ); - } - - // Clean up the test category - wp_unregister_ability_category( 'test' ); - } - /** * Unregisters only the test category. * diff --git a/tests/Fixtures/DummyErrorHandler.php b/tests/phpunit/Fixtures/DummyErrorHandler.php similarity index 100% rename from tests/Fixtures/DummyErrorHandler.php rename to tests/phpunit/Fixtures/DummyErrorHandler.php diff --git a/tests/Fixtures/DummyObservabilityHandler.php b/tests/phpunit/Fixtures/DummyObservabilityHandler.php similarity index 100% rename from tests/Fixtures/DummyObservabilityHandler.php rename to tests/phpunit/Fixtures/DummyObservabilityHandler.php diff --git a/tests/Fixtures/DummyTransport.php b/tests/phpunit/Fixtures/DummyTransport.php similarity index 100% rename from tests/Fixtures/DummyTransport.php rename to tests/phpunit/Fixtures/DummyTransport.php diff --git a/tests/Integration/BuilderPromptExecutionTest.php b/tests/phpunit/Integration/BuilderPromptExecutionTest.php similarity index 100% rename from tests/Integration/BuilderPromptExecutionTest.php rename to tests/phpunit/Integration/BuilderPromptExecutionTest.php diff --git a/tests/Integration/ErrorHandlingIntegrationTest.php b/tests/phpunit/Integration/ErrorHandlingIntegrationTest.php similarity index 100% rename from tests/Integration/ErrorHandlingIntegrationTest.php rename to tests/phpunit/Integration/ErrorHandlingIntegrationTest.php diff --git a/tests/Integration/HttpTransportTest.php b/tests/phpunit/Integration/HttpTransportTest.php similarity index 100% rename from tests/Integration/HttpTransportTest.php rename to tests/phpunit/Integration/HttpTransportTest.php diff --git a/tests/Integration/RegistrationValidationTest.php b/tests/phpunit/Integration/RegistrationValidationTest.php similarity index 100% rename from tests/Integration/RegistrationValidationTest.php rename to tests/phpunit/Integration/RegistrationValidationTest.php diff --git a/tests/Integration/TransportRoutingTest.php b/tests/phpunit/Integration/TransportRoutingTest.php similarity index 100% rename from tests/Integration/TransportRoutingTest.php rename to tests/phpunit/Integration/TransportRoutingTest.php diff --git a/tests/Integration/WordPressFiltersTest.php b/tests/phpunit/Integration/WordPressFiltersTest.php similarity index 100% rename from tests/Integration/WordPressFiltersTest.php rename to tests/phpunit/Integration/WordPressFiltersTest.php diff --git a/tests/Stubs/WpCliClasses.php b/tests/phpunit/Stubs/WpCliClasses.php similarity index 100% rename from tests/Stubs/WpCliClasses.php rename to tests/phpunit/Stubs/WpCliClasses.php diff --git a/tests/Stubs/WpCliStubs.php b/tests/phpunit/Stubs/WpCliStubs.php similarity index 100% rename from tests/Stubs/WpCliStubs.php rename to tests/phpunit/Stubs/WpCliStubs.php diff --git a/tests/Stubs/WpCliUtils.php b/tests/phpunit/Stubs/WpCliUtils.php similarity index 100% rename from tests/Stubs/WpCliUtils.php rename to tests/phpunit/Stubs/WpCliUtils.php diff --git a/tests/TestCase.php b/tests/phpunit/TestCase.php similarity index 56% rename from tests/TestCase.php rename to tests/phpunit/TestCase.php index ca933e33..9477bfc0 100644 --- a/tests/TestCase.php +++ b/tests/phpunit/TestCase.php @@ -9,17 +9,13 @@ namespace WP\MCP\Tests; -use WP\MCP\Abilities\DiscoverAbilitiesAbility; -use WP\MCP\Abilities\ExecuteAbilityAbility; -use WP\MCP\Abilities\GetAbilityInfoAbility; use WP\MCP\Core\McpServer; use WP\MCP\Tests\Fixtures\DummyAbility; use WP\MCP\Tests\Fixtures\DummyErrorHandler; use WP\MCP\Tests\Fixtures\DummyObservabilityHandler; -use Yoast\PHPUnitPolyfills\TestCases\TestCase as PolyfillsTestCase; -use WP_Error; +use WP_UnitTestCase; -abstract class TestCase extends PolyfillsTestCase { +abstract class TestCase extends WP_UnitTestCase { /** * Set up before each test class to ensure abilities are registered. @@ -61,54 +57,8 @@ static function () { // Use DummyAbility to register test category add_action( 'wp_abilities_api_categories_init', array( DummyAbility::class, 'register_category' ) ); - // Ensure categories API is initialized first - if ( ! did_action( 'wp_abilities_api_categories_init' ) ) { - do_action( 'wp_abilities_api_categories_init' ); - } - // Use DummyAbility to register test abilities add_action( 'wp_abilities_api_init', array( DummyAbility::class, 'register_abilities' ) ); - - // Register the default MCP abilities inside the hook - add_action( - 'wp_abilities_api_init', - static function () { - // Only register if they don't already exist to prevent duplicates - if ( ! wp_get_ability( 'mcp-adapter/discover-abilities' ) ) { - DiscoverAbilitiesAbility::register(); - } - if ( ! wp_get_ability( 'mcp-adapter/get-ability-info' ) ) { - GetAbilityInfoAbility::register(); - } - if ( ! wp_get_ability( 'mcp-adapter/execute-ability' ) ) { - ExecuteAbilityAbility::register(); - } - } - ); - - // Ensure abilities API is initialized so MCP abilities can be registered - if ( ! did_action( 'wp_abilities_api_init' ) ) { - do_action( 'wp_abilities_api_init' ); - } - } - - /** - * Clean up after each test class finishes. - * - * Note: We intentionally do NOT unregister test abilities here. - * Test fixtures from DummyAbility are designed to persist for the entire - * test suite run. This is necessary because WordPress hooks - * (wp_abilities_api_init, wp_abilities_api_categories_init) can only be fired - * once during the test suite execution. Re-registering between test classes - * would fail since the hooks have already been executed. - * - * This approach differs from abilities-api's test pattern, which registers - * fixtures per-test in set_up(). We use per-class registration with global - * persistence because our DummyAbility fixtures are designed as stable, - * reusable test helpers that don't interfere with test isolation. - */ - public static function tear_down_after_class(): void { - parent::tear_down_after_class(); } /** @@ -163,30 +113,6 @@ public function makeServer( array $tools = array(), array $resources = array(), ); } - /** - * Asserts that the given value is an instance of WP_Error. - * - * @param mixed $actual The value to check. - * @param string $message Optional. Message to display when the assertion fails. - * - * @return void - */ - public function assertWPError( $actual, string $message = '' ): void { - $this->assertInstanceOf( WP_Error::class, $actual, $message ); - } - - /** - * Asserts that the given value is not an instance of WP_Error. - * - * @param mixed $actual The value to check. - * @param string $message Optional. Message to display when the assertion fails. - * - * @return void - */ - public function assertNotWPError( $actual, string $message = '' ): void { - $this->assertNotInstanceOf( WP_Error::class, $actual, $message ); - } - /** * Captured `_doing_it_wrong` calls during a test. * @@ -242,36 +168,4 @@ protected function register_ability_in_hook( string $name, array $args ): void { // Clean up the callback to prevent duplicate registrations if hook fires again remove_action( 'wp_abilities_api_init', $callback, 999 ); } - - /** - * Asserts that `_doing_it_wrong` was triggered for the expected function. - * - * @param string $the_method Function name expected to trigger `_doing_it_wrong`. - * @param string|null $message_contains Optional. String that should be contained in the error message. - * - * @return void - */ - protected function assertDoingItWrongTriggered( string $the_method, ?string $message_contains = null ): void { - foreach ( $this->doing_it_wrong_log as $entry ) { - if ( $the_method === $entry['function'] ) { - // If message check is specified, verify it contains the expected text. - if ( null !== $message_contains && false === strpos( $entry['message'], $message_contains ) ) { - continue; - } - return; - } - } - - if ( null !== $message_contains ) { - $this->fail( - sprintf( - 'Failed asserting that _doing_it_wrong() was triggered for %s with message containing "%s".', - $the_method, - $message_contains - ) - ); - } else { - $this->fail( sprintf( 'Failed asserting that _doing_it_wrong() was triggered for %s.', $the_method ) ); - } - } } diff --git a/tests/Unit/Abilities/DiscoverAbilitiesAbilityTest.php b/tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php similarity index 100% rename from tests/Unit/Abilities/DiscoverAbilitiesAbilityTest.php rename to tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php diff --git a/tests/Unit/Abilities/ExecuteAbilityAbilityTest.php b/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php similarity index 100% rename from tests/Unit/Abilities/ExecuteAbilityAbilityTest.php rename to tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php diff --git a/tests/Unit/Abilities/GetAbilityInfoAbilityTest.php b/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php similarity index 100% rename from tests/Unit/Abilities/GetAbilityInfoAbilityTest.php rename to tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php diff --git a/tests/Unit/Cli/McpCommandTest.php b/tests/phpunit/Unit/Cli/McpCommandTest.php similarity index 100% rename from tests/Unit/Cli/McpCommandTest.php rename to tests/phpunit/Unit/Cli/McpCommandTest.php diff --git a/tests/Unit/Cli/StdioServerBridgeTest.php b/tests/phpunit/Unit/Cli/StdioServerBridgeTest.php similarity index 100% rename from tests/Unit/Cli/StdioServerBridgeTest.php rename to tests/phpunit/Unit/Cli/StdioServerBridgeTest.php diff --git a/tests/Unit/Core/DeveloperErrorsTest.php b/tests/phpunit/Unit/Core/DeveloperErrorsTest.php similarity index 70% rename from tests/Unit/Core/DeveloperErrorsTest.php rename to tests/phpunit/Unit/Core/DeveloperErrorsTest.php index a1a014fb..c5a5d8fc 100644 --- a/tests/Unit/Core/DeveloperErrorsTest.php +++ b/tests/phpunit/Unit/Core/DeveloperErrorsTest.php @@ -29,6 +29,7 @@ public function set_up(): void { public function test_creating_server_outside_mcp_adapter_init_triggers_doing_it_wrong(): void { // Try to create server outside of mcp_adapter_init + $this->setExpectedIncorrectUsage( 'create_server' ); $result = $this->adapter->create_server( 'test-server', 'mcp/v1', @@ -44,9 +45,6 @@ public function test_creating_server_outside_mcp_adapter_init_triggers_doing_it_ // Should return WP_Error $this->assertWPError( $result ); $this->assertSame( 'invalid_timing', $result->get_error_code() ); - - // Verify _doing_it_wrong was called - $this->assertDoingItWrongTriggered( 'create_server', 'mcp_adapter_init' ); } public function test_duplicate_server_id_triggers_doing_it_wrong(): void { @@ -71,6 +69,7 @@ public function test_duplicate_server_id_triggers_doing_it_wrong(): void { $this->assertNotWPError( $first_result ); // Try to create second server with same ID + $this->setExpectedIncorrectUsage( 'create_server' ); $second_result = $this->adapter->create_server( 'duplicate-id', 'mcp/v1', @@ -89,9 +88,6 @@ public function test_duplicate_server_id_triggers_doing_it_wrong(): void { // Second server should return WP_Error $this->assertWPError( $second_result ); $this->assertSame( 'duplicate_server_id', $second_result->get_error_code() ); - - // Verify _doing_it_wrong was called for duplicate ID - $this->assertDoingItWrongTriggered( 'create_server', 'already exists' ); } public function test_transport_factory_with_nonexistent_class_triggers_doing_it_wrong(): void { @@ -110,11 +106,8 @@ public function test_transport_factory_with_nonexistent_class_triggers_doing_it_ $factory = new McpTransportFactory( $server ); // Try to initialize with nonexistent transport class + $this->setExpectedIncorrectUsage( 'initialize_transports' ); $factory->initialize_transports( array( 'NonExistentTransportClass' ) ); - - // Verify _doing_it_wrong was called - $this->assertNotEmpty( $this->doing_it_wrong_log, 'Expected _doing_it_wrong to be called' ); - $this->assertDoingItWrongTriggered( 'initialize_transports', 'does not exist' ); } public function test_transport_factory_with_invalid_interface_triggers_doing_it_wrong(): void { @@ -133,48 +126,8 @@ public function test_transport_factory_with_invalid_interface_triggers_doing_it_ $factory = new McpTransportFactory( $server ); // Try to initialize with class that doesn't implement McpTransportInterface + $this->setExpectedIncorrectUsage( 'initialize_transports' ); $factory->initialize_transports( array( \stdClass::class ) ); - - // Verify _doing_it_wrong was called - $this->assertNotEmpty( $this->doing_it_wrong_log, 'Expected _doing_it_wrong to be called' ); - $this->assertDoingItWrongTriggered( 'initialize_transports', 'must implement' ); - } - - public function test_doing_it_wrong_messages_are_helpful_for_developers(): void { - // Test various error scenarios - - // 1. Server creation outside hook - $this->adapter->create_server( - 'test', - 'mcp/v1', - '/mcp', - 'Test', - 'Test', - '1.0.0', - array( DummyTransport::class ), - NullMcpErrorHandler::class - ); - - // 2. Transport with wrong interface - $server = new McpServer( - 'test-server', - 'mcp/v1', - '/mcp', - 'Test Server', - 'Test Description', - '1.0.0', - array(), - NullMcpErrorHandler::class, - NullMcpObservabilityHandler::class - ); - - $factory = new McpTransportFactory( $server ); - $factory->initialize_transports( array( \stdClass::class ) ); - - // Verify _doing_it_wrong calls were made - $this->assertNotEmpty( $this->doing_it_wrong_log, 'Expected _doing_it_wrong calls to be captured' ); - $this->assertDoingItWrongTriggered( 'create_server' ); - $this->assertDoingItWrongTriggered( 'initialize_transports' ); } public function test_no_doing_it_wrong_when_everything_is_correct(): void { @@ -200,8 +153,5 @@ public function test_no_doing_it_wrong_when_everything_is_correct(): void { // Should succeed without WP_Error $this->assertNotWPError( $result ); - - // Should be no _doing_it_wrong calls - $this->assertEmpty( $this->doing_it_wrong_log ); } } diff --git a/tests/Unit/Core/McpAdapterConfigTest.php b/tests/phpunit/Unit/Core/McpAdapterConfigTest.php similarity index 98% rename from tests/Unit/Core/McpAdapterConfigTest.php rename to tests/phpunit/Unit/Core/McpAdapterConfigTest.php index e92fa7d3..cd0c99d7 100644 --- a/tests/Unit/Core/McpAdapterConfigTest.php +++ b/tests/phpunit/Unit/Core/McpAdapterConfigTest.php @@ -350,15 +350,13 @@ static function ( $defaults ) { global $wp_current_filter; $wp_current_filter[] = 'mcp_adapter_init'; - // Call DefaultServerFactory::create() directly to test error handling + // Call DefaultServerFactory::create() directly to test error handling4 + $this->setExpectedIncorrectUsage( \WP\MCP\Servers\DefaultServerFactory::class . '::create' ); \WP\MCP\Servers\DefaultServerFactory::create(); // Clean up the filter mock array_pop( $wp_current_filter ); - // Verify _doing_it_wrong was called by DefaultServerFactory - $this->assertDoingItWrongTriggered( 'WP\MCP\Servers\DefaultServerFactory::create' ); - // Verify server was not created due to error $server = $this->adapter->get_server( 'mcp-adapter-default-server' ); $this->assertNull( $server, 'Server should not be created when create_server returns WP_Error' ); diff --git a/tests/Unit/Core/McpAdapterErrorHandlingTest.php b/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php similarity index 100% rename from tests/Unit/Core/McpAdapterErrorHandlingTest.php rename to tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php diff --git a/tests/Unit/Core/McpComponentRegistryTest.php b/tests/phpunit/Unit/Core/McpComponentRegistryTest.php similarity index 100% rename from tests/Unit/Core/McpComponentRegistryTest.php rename to tests/phpunit/Unit/Core/McpComponentRegistryTest.php diff --git a/tests/Unit/Core/McpTransportFactoryTest.php b/tests/phpunit/Unit/Core/McpTransportFactoryTest.php similarity index 89% rename from tests/Unit/Core/McpTransportFactoryTest.php rename to tests/phpunit/Unit/Core/McpTransportFactoryTest.php index f87e56b9..cea24512 100644 --- a/tests/Unit/Core/McpTransportFactoryTest.php +++ b/tests/phpunit/Unit/Core/McpTransportFactoryTest.php @@ -27,7 +27,7 @@ */ final class McpTransportFactoryTest extends TestCase { - private McpTransportFactory $factory; + private McpTransportFactory $transport_factory; private McpServer $server; public function set_up(): void { @@ -45,11 +45,11 @@ public function set_up(): void { DummyObservabilityHandler::class ); - $this->factory = new McpTransportFactory( $this->server ); + $this->transport_factory = new McpTransportFactory( $this->server ); } public function test_create_transport_context(): void { - $context = $this->factory->create_transport_context(); + $context = $this->transport_factory->create_transport_context(); $this->assertInstanceOf( McpTransportContext::class, $context ); @@ -76,7 +76,7 @@ public function test_create_transport_context(): void { public function test_initialize_transports_with_valid_transport(): void { // This should not throw an exception - $this->factory->initialize_transports( array( DummyTransport::class ) ); + $this->transport_factory->initialize_transports( array( DummyTransport::class ) ); // If we get here, the transport was successfully initialized $this->assertTrue( true ); @@ -84,7 +84,7 @@ public function test_initialize_transports_with_valid_transport(): void { public function test_initialize_transports_with_nonexistent_class(): void { // This should trigger _doing_it_wrong but not throw exception - $this->factory->initialize_transports( array( 'NonExistentTransportClass' ) ); + $this->transport_factory->initialize_transports( array( 'NonExistentTransportClass' ) ); // If we get here without exception, the method handled the nonexistent class gracefully $this->assertTrue( true ); @@ -93,7 +93,7 @@ public function test_initialize_transports_with_nonexistent_class(): void { public function test_initialize_transports_with_invalid_interface(): void { // This should trigger _doing_it_wrong but not throw exception // The method logs the error and continues processing other transports - $this->factory->initialize_transports( array( \stdClass::class ) ); + $this->transport_factory->initialize_transports( array( \stdClass::class ) ); // If we get here without exception, the method handled the invalid interface gracefully $this->assertTrue( true ); @@ -101,7 +101,7 @@ public function test_initialize_transports_with_invalid_interface(): void { public function test_initialize_transports_with_multiple_transports(): void { // Test with multiple valid transports - $this->factory->initialize_transports( + $this->transport_factory->initialize_transports( array( DummyTransport::class, DummyTransport::class, // Same transport twice should work @@ -114,7 +114,7 @@ public function test_initialize_transports_with_multiple_transports(): void { public function test_initialize_transports_with_mixed_validity(): void { // Mix valid and invalid transports - $this->factory->initialize_transports( + $this->transport_factory->initialize_transports( array( 'NonExistentClass', DummyTransport::class, // This should still work @@ -127,15 +127,15 @@ public function test_initialize_transports_with_mixed_validity(): void { public function test_initialize_transports_with_empty_array(): void { // Empty array should not cause issues - $this->factory->initialize_transports( array() ); + $this->transport_factory->initialize_transports( array() ); // If we get here, empty array was handled gracefully $this->assertTrue( true ); } public function test_create_transport_context_creates_fresh_handlers(): void { - $context1 = $this->factory->create_transport_context(); - $context2 = $this->factory->create_transport_context(); + $context1 = $this->transport_factory->create_transport_context(); + $context2 = $this->transport_factory->create_transport_context(); // Contexts should be different instances $this->assertNotSame( $context1, $context2 ); diff --git a/tests/Unit/Core/McpVersionNegotiatorTest.php b/tests/phpunit/Unit/Core/McpVersionNegotiatorTest.php similarity index 100% rename from tests/Unit/Core/McpVersionNegotiatorTest.php rename to tests/phpunit/Unit/Core/McpVersionNegotiatorTest.php diff --git a/tests/Unit/Domain/Prompts/McpPromptValidatorTest.php b/tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php similarity index 100% rename from tests/Unit/Domain/Prompts/McpPromptValidatorTest.php rename to tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php diff --git a/tests/Unit/Domain/Resources/McpResourceValidatorTest.php b/tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php similarity index 100% rename from tests/Unit/Domain/Resources/McpResourceValidatorTest.php rename to tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php diff --git a/tests/Unit/Domain/Tools/McpToolValidatorTest.php b/tests/phpunit/Unit/Domain/Tools/McpToolValidatorTest.php similarity index 100% rename from tests/Unit/Domain/Tools/McpToolValidatorTest.php rename to tests/phpunit/Unit/Domain/Tools/McpToolValidatorTest.php diff --git a/tests/Unit/Domain/Utils/AbilityArgumentNormalizerTest.php b/tests/phpunit/Unit/Domain/Utils/AbilityArgumentNormalizerTest.php similarity index 100% rename from tests/Unit/Domain/Utils/AbilityArgumentNormalizerTest.php rename to tests/phpunit/Unit/Domain/Utils/AbilityArgumentNormalizerTest.php diff --git a/tests/Unit/Domain/Utils/ContentBlockHelperTest.php b/tests/phpunit/Unit/Domain/Utils/ContentBlockHelperTest.php similarity index 100% rename from tests/Unit/Domain/Utils/ContentBlockHelperTest.php rename to tests/phpunit/Unit/Domain/Utils/ContentBlockHelperTest.php diff --git a/tests/Unit/Domain/Utils/McpAnnotationMapperTest.php b/tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php similarity index 100% rename from tests/Unit/Domain/Utils/McpAnnotationMapperTest.php rename to tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php diff --git a/tests/Unit/Domain/Utils/McpNameSanitizerTest.php b/tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php similarity index 100% rename from tests/Unit/Domain/Utils/McpNameSanitizerTest.php rename to tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php diff --git a/tests/Unit/Domain/Utils/McpValidatorTest.php b/tests/phpunit/Unit/Domain/Utils/McpValidatorTest.php similarity index 100% rename from tests/Unit/Domain/Utils/McpValidatorTest.php rename to tests/phpunit/Unit/Domain/Utils/McpValidatorTest.php diff --git a/tests/Unit/Domain/Utils/SchemaTransformerTest.php b/tests/phpunit/Unit/Domain/Utils/SchemaTransformerTest.php similarity index 100% rename from tests/Unit/Domain/Utils/SchemaTransformerTest.php rename to tests/phpunit/Unit/Domain/Utils/SchemaTransformerTest.php diff --git a/tests/Unit/ErrorHandlers/ErrorEnvelopeTest.php b/tests/phpunit/Unit/ErrorHandlers/ErrorEnvelopeTest.php similarity index 100% rename from tests/Unit/ErrorHandlers/ErrorEnvelopeTest.php rename to tests/phpunit/Unit/ErrorHandlers/ErrorEnvelopeTest.php diff --git a/tests/Unit/ErrorHandlers/McpErrorHandlerInterfaceTest.php b/tests/phpunit/Unit/ErrorHandlers/McpErrorHandlerInterfaceTest.php similarity index 100% rename from tests/Unit/ErrorHandlers/McpErrorHandlerInterfaceTest.php rename to tests/phpunit/Unit/ErrorHandlers/McpErrorHandlerInterfaceTest.php diff --git a/tests/Unit/ErrorHandling/ErrorResponseConsistencyTest.php b/tests/phpunit/Unit/ErrorHandling/ErrorResponseConsistencyTest.php similarity index 100% rename from tests/Unit/ErrorHandling/ErrorResponseConsistencyTest.php rename to tests/phpunit/Unit/ErrorHandling/ErrorResponseConsistencyTest.php diff --git a/tests/Unit/Handlers/HandlerHelperTraitTest.php b/tests/phpunit/Unit/Handlers/HandlerHelperTraitTest.php similarity index 100% rename from tests/Unit/Handlers/HandlerHelperTraitTest.php rename to tests/phpunit/Unit/Handlers/HandlerHelperTraitTest.php diff --git a/tests/Unit/Handlers/InitializeHandlerTest.php b/tests/phpunit/Unit/Handlers/InitializeHandlerTest.php similarity index 100% rename from tests/Unit/Handlers/InitializeHandlerTest.php rename to tests/phpunit/Unit/Handlers/InitializeHandlerTest.php diff --git a/tests/Unit/Handlers/PromptsHandlerTest.php b/tests/phpunit/Unit/Handlers/PromptsHandlerTest.php similarity index 100% rename from tests/Unit/Handlers/PromptsHandlerTest.php rename to tests/phpunit/Unit/Handlers/PromptsHandlerTest.php diff --git a/tests/Unit/Handlers/ResourcesHandlerListTest.php b/tests/phpunit/Unit/Handlers/ResourcesHandlerListTest.php similarity index 100% rename from tests/Unit/Handlers/ResourcesHandlerListTest.php rename to tests/phpunit/Unit/Handlers/ResourcesHandlerListTest.php diff --git a/tests/Unit/Handlers/ResourcesHandlerReadTest.php b/tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php similarity index 100% rename from tests/Unit/Handlers/ResourcesHandlerReadTest.php rename to tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php diff --git a/tests/Unit/Handlers/ResourcesHandlerTest.php b/tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php similarity index 100% rename from tests/Unit/Handlers/ResourcesHandlerTest.php rename to tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php diff --git a/tests/Unit/Handlers/SystemHandlerTest.php b/tests/phpunit/Unit/Handlers/SystemHandlerTest.php similarity index 100% rename from tests/Unit/Handlers/SystemHandlerTest.php rename to tests/phpunit/Unit/Handlers/SystemHandlerTest.php diff --git a/tests/Unit/Handlers/ToolsHandlerCallTest.php b/tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php similarity index 100% rename from tests/Unit/Handlers/ToolsHandlerCallTest.php rename to tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php diff --git a/tests/Unit/Handlers/ToolsHandlerListTest.php b/tests/phpunit/Unit/Handlers/ToolsHandlerListTest.php similarity index 100% rename from tests/Unit/Handlers/ToolsHandlerListTest.php rename to tests/phpunit/Unit/Handlers/ToolsHandlerListTest.php diff --git a/tests/Unit/Handlers/ToolsHandlerTest.php b/tests/phpunit/Unit/Handlers/ToolsHandlerTest.php similarity index 100% rename from tests/Unit/Handlers/ToolsHandlerTest.php rename to tests/phpunit/Unit/Handlers/ToolsHandlerTest.php diff --git a/tests/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php b/tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php similarity index 100% rename from tests/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php rename to tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php diff --git a/tests/Unit/Infrastructure/ErrorHandling/McpErrorFactoryTest.php b/tests/phpunit/Unit/Infrastructure/ErrorHandling/McpErrorFactoryTest.php similarity index 100% rename from tests/Unit/Infrastructure/ErrorHandling/McpErrorFactoryTest.php rename to tests/phpunit/Unit/Infrastructure/ErrorHandling/McpErrorFactoryTest.php diff --git a/tests/Unit/Infrastructure/ErrorHandling/NullMcpErrorHandlerTest.php b/tests/phpunit/Unit/Infrastructure/ErrorHandling/NullMcpErrorHandlerTest.php similarity index 100% rename from tests/Unit/Infrastructure/ErrorHandling/NullMcpErrorHandlerTest.php rename to tests/phpunit/Unit/Infrastructure/ErrorHandling/NullMcpErrorHandlerTest.php diff --git a/tests/Unit/Infrastructure/Observability/ErrorLogMcpObservabilityHandlerTest.php b/tests/phpunit/Unit/Infrastructure/Observability/ErrorLogMcpObservabilityHandlerTest.php similarity index 100% rename from tests/Unit/Infrastructure/Observability/ErrorLogMcpObservabilityHandlerTest.php rename to tests/phpunit/Unit/Infrastructure/Observability/ErrorLogMcpObservabilityHandlerTest.php diff --git a/tests/Unit/Infrastructure/Observability/FailureReasonTest.php b/tests/phpunit/Unit/Infrastructure/Observability/FailureReasonTest.php similarity index 100% rename from tests/Unit/Infrastructure/Observability/FailureReasonTest.php rename to tests/phpunit/Unit/Infrastructure/Observability/FailureReasonTest.php diff --git a/tests/Unit/Infrastructure/Observability/McpObservabilityHelperTraitTest.php b/tests/phpunit/Unit/Infrastructure/Observability/McpObservabilityHelperTraitTest.php similarity index 100% rename from tests/Unit/Infrastructure/Observability/McpObservabilityHelperTraitTest.php rename to tests/phpunit/Unit/Infrastructure/Observability/McpObservabilityHelperTraitTest.php diff --git a/tests/Unit/McpServerTest.php b/tests/phpunit/Unit/McpServerTest.php similarity index 100% rename from tests/Unit/McpServerTest.php rename to tests/phpunit/Unit/McpServerTest.php diff --git a/tests/Unit/McpTransportTest.php b/tests/phpunit/Unit/McpTransportTest.php similarity index 100% rename from tests/Unit/McpTransportTest.php rename to tests/phpunit/Unit/McpTransportTest.php diff --git a/tests/Unit/Observability/NullHandlerTest.php b/tests/phpunit/Unit/Observability/NullHandlerTest.php similarity index 100% rename from tests/Unit/Observability/NullHandlerTest.php rename to tests/phpunit/Unit/Observability/NullHandlerTest.php diff --git a/tests/phpunit/Unit/PluginTest.php b/tests/phpunit/Unit/PluginTest.php new file mode 100644 index 00000000..55fd0ba4 --- /dev/null +++ b/tests/phpunit/Unit/PluginTest.php @@ -0,0 +1,28 @@ +setExpectedIncorrectUsage( '__clone' ); + clone $plugin; + } + + public function test_plugin_wakeup_triggers_doing_it_wrong(): void { + $plugin = Plugin::instance(); + + // Attempt to unserialize the plugin + $serialized = serialize( $plugin ); + $this->setExpectedIncorrectUsage( '__wakeup' ); + unserialize( $serialized ); + } +} diff --git a/tests/Unit/Prompts/McpPromptBuilderTest.php b/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php similarity index 100% rename from tests/Unit/Prompts/McpPromptBuilderTest.php rename to tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php diff --git a/tests/Unit/Prompts/McpPromptTest.php b/tests/phpunit/Unit/Prompts/McpPromptTest.php similarity index 100% rename from tests/Unit/Prompts/McpPromptTest.php rename to tests/phpunit/Unit/Prompts/McpPromptTest.php diff --git a/tests/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php b/tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php similarity index 100% rename from tests/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php rename to tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php diff --git a/tests/Unit/Resources/McpResourceTest.php b/tests/phpunit/Unit/Resources/McpResourceTest.php similarity index 100% rename from tests/Unit/Resources/McpResourceTest.php rename to tests/phpunit/Unit/Resources/McpResourceTest.php diff --git a/tests/Unit/Resources/RegisterAbilityAsMcpResourceTest.php b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php similarity index 98% rename from tests/Unit/Resources/RegisterAbilityAsMcpResourceTest.php rename to tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php index 66f4286c..6067006a 100644 --- a/tests/Unit/Resources/RegisterAbilityAsMcpResourceTest.php +++ b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php @@ -235,6 +235,7 @@ public function test_invalid_annotations_are_dropped_with_doing_it_wrong(): void $ability = wp_get_ability( 'test/resource-invalid-annotations-new-meta' ); $this->assertNotNull( $ability, 'Ability test/resource-invalid-annotations-new-meta should be registered' ); + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_data' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); // Resource should still be created successfully (graceful degradation). @@ -244,9 +245,6 @@ public function test_invalid_annotations_are_dropped_with_doing_it_wrong(): void // Annotations should NOT be present (all dropped due to validation errors). $this->assertArrayNotHasKey( 'annotations', $arr ); - - // Verify _doing_it_wrong was triggered. - $this->assertDoingItWrongTriggered( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_data' ); } public function test_mixed_valid_invalid_annotations_drops_all(): void { diff --git a/tests/Unit/Servers/DefaultServerFactoryTest.php b/tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php similarity index 100% rename from tests/Unit/Servers/DefaultServerFactoryTest.php rename to tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php diff --git a/tests/Unit/Tools/McpToolTest.php b/tests/phpunit/Unit/Tools/McpToolTest.php similarity index 100% rename from tests/Unit/Tools/McpToolTest.php rename to tests/phpunit/Unit/Tools/McpToolTest.php diff --git a/tests/Unit/Tools/RegisterAbilityAsMcpToolTest.php b/tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php similarity index 100% rename from tests/Unit/Tools/RegisterAbilityAsMcpToolTest.php rename to tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php diff --git a/tests/Unit/Transport/Infrastructure/DtoSerializationRegressionTest.php b/tests/phpunit/Unit/Transport/Infrastructure/DtoSerializationRegressionTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/DtoSerializationRegressionTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/DtoSerializationRegressionTest.php diff --git a/tests/Unit/Transport/Infrastructure/HttpRequestHandlerTest.php b/tests/phpunit/Unit/Transport/Infrastructure/HttpRequestHandlerTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/HttpRequestHandlerTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/HttpRequestHandlerTest.php diff --git a/tests/Unit/Transport/Infrastructure/HttpSessionValidatorTest.php b/tests/phpunit/Unit/Transport/Infrastructure/HttpSessionValidatorTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/HttpSessionValidatorTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/HttpSessionValidatorTest.php diff --git a/tests/Unit/Transport/Infrastructure/JsonRpcResponseBuilderTest.php b/tests/phpunit/Unit/Transport/Infrastructure/JsonRpcResponseBuilderTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/JsonRpcResponseBuilderTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/JsonRpcResponseBuilderTest.php diff --git a/tests/Unit/Transport/Infrastructure/McpTransportContextTest.php b/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/McpTransportContextTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php diff --git a/tests/Unit/Transport/Infrastructure/RequestRouterTest.php b/tests/phpunit/Unit/Transport/Infrastructure/RequestRouterTest.php similarity index 100% rename from tests/Unit/Transport/Infrastructure/RequestRouterTest.php rename to tests/phpunit/Unit/Transport/Infrastructure/RequestRouterTest.php diff --git a/tests/Unit/Transport/McpSessionManagerTest.php b/tests/phpunit/Unit/Transport/McpSessionManagerTest.php similarity index 100% rename from tests/Unit/Transport/McpSessionManagerTest.php rename to tests/phpunit/Unit/Transport/McpSessionManagerTest.php diff --git a/tests/bootstrap.php b/tests/phpunit/bootstrap.php similarity index 88% rename from tests/bootstrap.php rename to tests/phpunit/bootstrap.php index a5e898b6..cbf0c3ac 100644 --- a/tests/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -8,7 +8,9 @@ * phpcs:disable WordPressVIPMinimum.Files.IncludingFile.UsingVariable */ -define( 'TESTS_REPO_ROOT_DIR', dirname( __DIR__ ) ); +declare( strict_types = 1 ); + +define( 'TESTS_REPO_ROOT_DIR', dirname( __DIR__, 2 ) ); // Set custom debug log location for tests. define( 'WP_DEBUG_LOG_FILE', TESTS_REPO_ROOT_DIR . '/tests/_output/debug.log' ); @@ -38,10 +40,8 @@ tests_add_filter( 'muplugins_loaded', static function (): void { - // Load the abilities API, next to the MCP adapter. - require_once dirname( __DIR__, 2 ) . '/abilities-api/abilities-api.php'; - // Load the MCP adapter. - require_once dirname( __DIR__ ) . '/mcp-adapter.php'; + // Require ( to bypass require_once ). + require TESTS_REPO_ROOT_DIR . '/mcp-adapter.php'; } ); From bf6555d9ce0b09e5256d6af8186ed3bc9b573cc6 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Sun, 17 May 2026 21:38:57 +0300 Subject: [PATCH 3/6] tests: phpcbf --- .../ErrorHandlingIntegrationTest.php | 2 +- .../phpunit/Integration/HttpTransportTest.php | 2 +- .../RegistrationValidationTest.php | 10 +- .../Abilities/GetAbilityInfoAbilityTest.php | 10 +- .../Unit/Core/McpAdapterErrorHandlingTest.php | 1 - .../Domain/Prompts/McpPromptValidatorTest.php | 2 +- .../Resources/McpResourceValidatorTest.php | 8 +- .../Domain/Utils/McpAnnotationMapperTest.php | 26 +- .../Domain/Utils/McpNameSanitizerTest.php | 1 - .../Handlers/ResourcesHandlerReadTest.php | 5 +- .../Unit/Handlers/ToolsHandlerCallTest.php | 4 +- .../ErrorLogMcpErrorHandlerTest.php | 1 - .../Unit/Prompts/McpPromptBuilderTest.php | 30 +-- .../RegisterAbilityAsMcpPromptTest.php | 252 +++++++++--------- .../RegisterAbilityAsMcpResourceTest.php | 18 +- .../Unit/Servers/DefaultServerFactoryTest.php | 7 +- .../Tools/RegisterAbilityAsMcpToolTest.php | 20 +- .../McpTransportContextTest.php | 14 +- .../Unit/Transport/McpSessionManagerTest.php | 4 +- 19 files changed, 208 insertions(+), 209 deletions(-) diff --git a/tests/phpunit/Integration/ErrorHandlingIntegrationTest.php b/tests/phpunit/Integration/ErrorHandlingIntegrationTest.php index aea5b02f..4804682c 100644 --- a/tests/phpunit/Integration/ErrorHandlingIntegrationTest.php +++ b/tests/phpunit/Integration/ErrorHandlingIntegrationTest.php @@ -136,7 +136,7 @@ public function test_json_rpc_validation_methods(): void { 'method' => 'test', 'id' => 1, ); - $result = McpErrorFactory::validate_jsonrpc_message( $invalid_message ); + $result = McpErrorFactory::validate_jsonrpc_message( $invalid_message ); $this->assertInstanceOf( JSONRPCErrorResponse::class, $result ); $this->assertNotNull( $result->getError() ); diff --git a/tests/phpunit/Integration/HttpTransportTest.php b/tests/phpunit/Integration/HttpTransportTest.php index a18ca333..48bb29f7 100644 --- a/tests/phpunit/Integration/HttpTransportTest.php +++ b/tests/phpunit/Integration/HttpTransportTest.php @@ -21,9 +21,9 @@ use WP\MCP\Tests\TestCase; use WP\MCP\Transport\HttpTransport; use WP\MCP\Transport\Infrastructure\McpTransportContext; +use WP_Error; use WP_REST_Request; use WP_REST_Response; -use WP_Error; /** * Test MCP HTTP Transport behavior against the MCP 2025-11-25 baseline. diff --git a/tests/phpunit/Integration/RegistrationValidationTest.php b/tests/phpunit/Integration/RegistrationValidationTest.php index b65544f6..686c5c72 100644 --- a/tests/phpunit/Integration/RegistrationValidationTest.php +++ b/tests/phpunit/Integration/RegistrationValidationTest.php @@ -17,7 +17,7 @@ public function test_invalid_tool_registration_is_logged_and_skipped(): void { add_filter( 'mcp_adapter_validation_enabled', '__return_true' ); // Force an invalid name that sanitizer won't fix (because it's post-sanitization filter) - $invalid_name_callback = fn() => 'invalid name'; + $invalid_name_callback = static fn() => 'invalid name'; add_filter( 'mcp_adapter_tool_name', $invalid_name_callback ); $ability_name = 'test/invalid-mcp-tool'; @@ -28,8 +28,8 @@ public function test_invalid_tool_registration_is_logged_and_skipped(): void { 'description' => 'A tool with invalid MCP name', 'category' => 'mcp-adapter', 'input_schema' => array( 'type' => 'object' ), - 'execute_callback' => fn() => array( 'ok' => true ), - 'permission_callback' => fn() => true, + 'execute_callback' => static fn() => array( 'ok' => true ), + 'permission_callback' => static fn() => true, ) ); @@ -73,8 +73,8 @@ public function test_validation_disabled_allows_invalid_dto_creation_if_possible 'description' => 'A tool for testing disabled validation', 'category' => 'mcp-adapter', 'input_schema' => array( 'type' => 'object' ), - 'execute_callback' => fn() => array( 'ok' => true ), - 'permission_callback' => fn() => true, + 'execute_callback' => static fn() => array( 'ok' => true ), + 'permission_callback' => static fn() => true, ) ); diff --git a/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php b/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php index 40b4fef0..7c919980 100644 --- a/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php +++ b/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php @@ -193,7 +193,7 @@ public function test_execute_with_ability_having_output_schema(): void { // Check if output schema is included when available $ability = wp_get_ability( 'test/always-allowed' ); $this->assertNotNull( $ability, 'Ability test/always-allowed should be registered' ); - + $output_schema = $ability->get_output_schema(); if ( empty( $output_schema ) ) { @@ -217,7 +217,7 @@ public function test_execute_with_ability_having_meta(): void { // Check if meta is included when available $ability = wp_get_ability( 'test/always-allowed' ); $this->assertNotNull( $ability, 'Ability test/always-allowed should be registered' ); - + $meta = $ability->get_meta(); if ( empty( $meta ) ) { @@ -264,7 +264,7 @@ public function test_execute_with_nonexistent_ability(): void { public function test_ability_has_correct_input_schema(): void { $ability = wp_get_ability( 'mcp-adapter/get-ability-info' ); $this->assertNotNull( $ability, 'Ability mcp-adapter/get-ability-info should be registered' ); - + $input_schema = $ability->get_input_schema(); $this->assertIsArray( $input_schema ); @@ -277,7 +277,7 @@ public function test_ability_has_correct_input_schema(): void { public function test_ability_has_correct_output_schema(): void { $ability = wp_get_ability( 'mcp-adapter/get-ability-info' ); $this->assertNotNull( $ability, 'Ability mcp-adapter/get-ability-info should be registered' ); - + $output_schema = $ability->get_output_schema(); $this->assertIsArray( $output_schema ); @@ -298,7 +298,7 @@ public function test_ability_has_correct_output_schema(): void { public function test_ability_has_correct_annotations(): void { $ability = wp_get_ability( 'mcp-adapter/get-ability-info' ); $this->assertNotNull( $ability, 'Ability mcp-adapter/get-ability-info should be registered' ); - + $meta = $ability->get_meta(); $this->assertIsArray( $meta ); diff --git a/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php b/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php index 97cb7bb0..dd83da58 100644 --- a/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php +++ b/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php @@ -236,4 +236,3 @@ public function test_create_server_returns_adapter_instance_on_success(): void { $this->assertNotNull( $server ); } } - diff --git a/tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php b/tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php index 8c05f29c..dfaac75c 100644 --- a/tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php +++ b/tests/phpunit/Unit/Domain/Prompts/McpPromptValidatorTest.php @@ -671,7 +671,7 @@ public function test_validate_prompt_instance_with_valid_prompt(): void { $mcp_prompt = McpPrompt::fromArray( array( 'name' => 'test-prompt', - 'handler' => function () { + 'handler' => static function () { return array( 'messages' => array() ); }, ) diff --git a/tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php b/tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php index 10af62e6..0dc1ca21 100644 --- a/tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php +++ b/tests/phpunit/Unit/Domain/Resources/McpResourceValidatorTest.php @@ -182,8 +182,8 @@ public function test_validate_resource_data_with_context_in_error_message(): voi public function test_get_validation_errors_with_valid_resource_data(): void { $resource_data = array( - 'uri' => 'test://resource', - 'text' => 'Content', + 'uri' => 'test://resource', + 'text' => 'Content', ); $errors = McpResourceValidator::get_validation_errors( $resource_data ); @@ -328,8 +328,8 @@ public function test_get_validation_errors_with_valid_mime_type(): void { public function test_get_validation_errors_reports_multiple_errors(): void { $resource_data = array( - 'uri' => 'invalid uri', - 'mimeType' => 'invalid-mime', + 'uri' => 'invalid uri', + 'mimeType' => 'invalid-mime', // Missing text/blob content ); diff --git a/tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php b/tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php index 9f52778e..9116cc14 100644 --- a/tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php +++ b/tests/phpunit/Unit/Domain/Utils/McpAnnotationMapperTest.php @@ -73,11 +73,11 @@ public function test_map_returns_empty_for_prompt_feature_type(): void { public function test_map_includes_tool_specific_fields(): void { $annotations = array( - 'readonly' => true, - 'destructive' => false, - 'idempotent' => true, + 'readonly' => true, + 'destructive' => false, + 'idempotent' => true, 'openWorldHint' => false, - 'title' => 'Tool Title', + 'title' => 'Tool Title', ); $result = McpAnnotationMapper::map( $annotations, 'tool' ); @@ -176,10 +176,10 @@ public function test_map_excludes_tool_fields_for_resource(): void { */ public function test_map_excludes_all_fields_for_prompt(): void { $annotations = array( - 'readonly' => true, - 'title' => 'Some Title', - 'priority' => 0.5, - 'audience' => array( 'user' ), + 'readonly' => true, + 'title' => 'Some Title', + 'priority' => 0.5, + 'audience' => array( 'user' ), ); $result = McpAnnotationMapper::map( $annotations, 'prompt' ); @@ -264,11 +264,11 @@ public function test_map_with_null_ability_property_uses_mcp_field_name_for_reso public function test_map_excludes_shared_annotations_from_tools(): void { // Per MCP 2025-11-25 spec, ToolAnnotations does NOT include shared Annotations fields. $annotations = array( - 'audience' => array( 'user' ), // Shared annotation - NOT for tools - 'lastModified' => '2024-01-15T10:30:00Z', // Shared annotation - NOT for tools - 'priority' => 0.5, // Shared annotation - NOT for tools - 'readOnlyHint' => true, // Tool annotation - should be included - 'title' => 'Test Title', // Tool annotation - should be included + 'audience' => array( 'user' ), // Shared annotation - NOT for tools + 'lastModified' => '2024-01-15T10:30:00Z', // Shared annotation - NOT for tools + 'priority' => 0.5, // Shared annotation - NOT for tools + 'readOnlyHint' => true, // Tool annotation - should be included + 'title' => 'Test Title', // Tool annotation - should be included ); $result = McpAnnotationMapper::map( $annotations, 'tool' ); diff --git a/tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php b/tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php index 43ca4fef..e339b6f8 100644 --- a/tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php +++ b/tests/phpunit/Unit/Domain/Utils/McpNameSanitizerTest.php @@ -11,7 +11,6 @@ use WP\MCP\Domain\Utils\McpNameSanitizer; use WP\MCP\Tests\TestCase; -use WP_Error; /** * Test McpNameSanitizer functionality. diff --git a/tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php b/tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php index 9d7cf83a..241ea092 100644 --- a/tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php +++ b/tests/phpunit/Unit/Handlers/ResourcesHandlerReadTest.php @@ -239,7 +239,10 @@ public function test_read_resource_wraps_non_array_result_as_json(): void { 'description' => 'Returns an object result', 'category' => 'test', 'execute_callback' => static function () { - return array( 'status' => 'ok', 'count' => 42 ); + return array( + 'status' => 'ok', + 'count' => 42, + ); }, 'permission_callback' => static function () { return true; diff --git a/tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php b/tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php index d53dbce5..99302e06 100644 --- a/tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php +++ b/tests/phpunit/Unit/Handlers/ToolsHandlerCallTest.php @@ -175,7 +175,7 @@ public function test_pre_tool_call_filter_can_modify_arguments(): void { $received_args = null; $filter = static function ( array $args, string $tool_name ) use ( &$received_args ): array { - $received_args = $args; + $received_args = $args; $args['injected_by_filter'] = true; return $args; @@ -292,7 +292,7 @@ public function test_tool_call_preserves_meta_in_text_and_structured_content(): $this->assertArrayHasKey( '_meta', $structured ); $this->assertArrayHasKey( 'mcp_adapter', $structured['_meta'] ); $this->assertArrayHasKey( 'mcp_adapter', $structured['nested']['_meta'] ); - } + } public function test_call_tool_with_string_arguments_returns_invalid_params_error(): void { $server = $this->makeServer( array( 'test/always-allowed' ) ); diff --git a/tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php b/tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php index 826082cb..4c46b6c5 100644 --- a/tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php +++ b/tests/phpunit/Unit/Infrastructure/ErrorHandling/ErrorLogMcpErrorHandlerTest.php @@ -74,4 +74,3 @@ public function test_log_handles_complex_context(): void { $this->assertTrue( true, 'log() method executed with complex context without throwing exception' ); } } - diff --git a/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php b/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php index eafe13ef..9e02739e 100644 --- a/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php +++ b/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php @@ -312,9 +312,9 @@ public function test_get_icons_returns_empty_array_when_not_set(): void { // User _meta Tests (MCP 2025-11-25) // ========================================================================= - public function test_builder_with_meta_includes_meta_in_prompt(): void { - $builder = new TestPromptWithMeta(); - $prompt = $builder->build(); + public function test_builder_with_meta_includes_meta_in_prompt(): void { + $builder = new TestPromptWithMeta(); + $prompt = $builder->build(); $arr = $prompt->toArray(); @@ -327,19 +327,19 @@ public function test_builder_with_meta_includes_meta_in_prompt(): void { $this->assertTrue( $arr['_meta']['custom_vendor']['feature_flag'] ); $this->assertSame( '2.0', $arr['_meta']['custom_vendor']['version'] ); - // Verify another_key is preserved. - $this->assertArrayHasKey( 'another_key', $arr['_meta'] ); - $this->assertSame( 'some-value', $arr['_meta']['another_key'] ); - } + // Verify another_key is preserved. + $this->assertArrayHasKey( 'another_key', $arr['_meta'] ); + $this->assertSame( 'some-value', $arr['_meta']['another_key'] ); + } - public function test_builder_without_user_meta_has_no_meta_field(): void { - $builder = new TestPrompt(); - $prompt = $builder->build(); + public function test_builder_without_user_meta_has_no_meta_field(): void { + $builder = new TestPrompt(); + $prompt = $builder->build(); - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); - $this->assertArrayNotHasKey( '_meta', $arr ); - } + $this->assertArrayNotHasKey( '_meta', $arr ); + } public function test_get_meta_returns_configured_meta(): void { $builder = new TestPromptWithMeta(); @@ -381,7 +381,7 @@ public function test_builder_with_both_icons_and_meta(): void { $this->assertArrayHasKey( '_meta', $arr ); $this->assertArrayHasKey( 'vendor_data', $arr['_meta'] ); $this->assertSame( 'value', $arr['_meta']['vendor_data']['key'] ); - } + } public function test_builder_with_icons_can_be_registered_with_server(): void { $server = $this->makeServer( array(), array(), array( TestPromptWithIcons::class ) ); @@ -463,7 +463,7 @@ public function test_mixed_getter_and_build_calls_do_not_accumulate(): void { // Verify argument names are correct (no duplicates). $args = $prompt->getArguments(); - $arg_names = array_map( fn( $arg ) => $arg->getName(), $args ); + $arg_names = array_map( static fn( $arg ) => $arg->getName(), $args ); $this->assertSame( array( 'code', 'language', 'focus' ), $arg_names ); } diff --git a/tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php b/tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php index fc28e95d..59af9bdf 100644 --- a/tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php +++ b/tests/phpunit/Unit/Prompts/RegisterAbilityAsMcpPromptTest.php @@ -8,18 +8,18 @@ use WP\MCP\Tests\TestCase; use WP\McpSchema\Server\Prompts\DTO\Prompt as PromptDto; - final class RegisterAbilityAsMcpPromptTest extends TestCase { - - public function test_make_builds_prompt_from_ability(): void { - $ability = wp_get_ability( 'test/prompt' ); - $this->assertNotNull( $ability, 'Ability test/prompt should be registered' ); - $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); - $this->assertInstanceOf( PromptDto::class, $prompt ); - $arr = $prompt->toArray(); - $this->assertSame( 'test-prompt', $arr['name'] ); - $this->assertArrayHasKey( 'arguments', $arr ); - $this->assertNull( $prompt->get_meta() ); - } +final class RegisterAbilityAsMcpPromptTest extends TestCase { + + public function test_make_builds_prompt_from_ability(): void { + $ability = wp_get_ability( 'test/prompt' ); + $this->assertNotNull( $ability, 'Ability test/prompt should be registered' ); + $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); + $this->assertInstanceOf( PromptDto::class, $prompt ); + $arr = $prompt->toArray(); + $this->assertSame( 'test-prompt', $arr['name'] ); + $this->assertArrayHasKey( 'arguments', $arr ); + $this->assertNull( $prompt->get_meta() ); + } public function test_annotations_are_mapped_to_mcp_format(): void { $ability = wp_get_ability( 'test/prompt-with-annotations' ); @@ -64,51 +64,51 @@ public function test_empty_annotations_are_not_included(): void { // Flattened Schema Tests // ========================================================================= - public function test_flattened_string_schema_creates_single_input_argument(): void { - $ability = wp_get_ability( 'test/prompt-flattened-string' ); - $this->assertNotNull( $ability, 'Ability test/prompt-flattened-string should be registered' ); + public function test_flattened_string_schema_creates_single_input_argument(): void { + $ability = wp_get_ability( 'test/prompt-flattened-string' ); + $this->assertNotNull( $ability, 'Ability test/prompt-flattened-string should be registered' ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); // Should have exactly one argument named 'input'. $this->assertArrayHasKey( 'arguments', $arr ); $this->assertCount( 1, $arr['arguments'] ); $this->assertSame( 'input', $arr['arguments'][0]['name'] ); - $this->assertSame( 'The code to review', $arr['arguments'][0]['description'] ); - $this->assertTrue( $arr['arguments'][0]['required'] ); + $this->assertSame( 'The code to review', $arr['arguments'][0]['description'] ); + $this->assertTrue( $arr['arguments'][0]['required'] ); - $adapter_meta = $built['adapter_meta']; - $this->assertTrue( $adapter_meta['input_schema_transformed'] ); - $this->assertSame( 'input', $adapter_meta['input_schema_wrapper'] ); - } + $adapter_meta = $built['adapter_meta']; + $this->assertTrue( $adapter_meta['input_schema_transformed'] ); + $this->assertSame( 'input', $adapter_meta['input_schema_wrapper'] ); + } - public function test_flattened_array_schema_creates_single_input_argument(): void { - $ability = wp_get_ability( 'test/prompt-flattened-array' ); - $this->assertNotNull( $ability, 'Ability test/prompt-flattened-array should be registered' ); + public function test_flattened_array_schema_creates_single_input_argument(): void { + $ability = wp_get_ability( 'test/prompt-flattened-array' ); + $this->assertNotNull( $ability, 'Ability test/prompt-flattened-array should be registered' ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); // Should have exactly one argument named 'input'. $this->assertArrayHasKey( 'arguments', $arr ); $this->assertCount( 1, $arr['arguments'] ); $this->assertSame( 'input', $arr['arguments'][0]['name'] ); - $this->assertSame( 'List of items to process', $arr['arguments'][0]['description'] ); - $this->assertTrue( $arr['arguments'][0]['required'] ); + $this->assertSame( 'List of items to process', $arr['arguments'][0]['description'] ); + $this->assertTrue( $arr['arguments'][0]['required'] ); - $this->assertTrue( $built['adapter_meta']['input_schema_transformed'] ); - } + $this->assertTrue( $built['adapter_meta']['input_schema_transformed'] ); + } // ========================================================================= // Property Title Mapping Tests @@ -320,19 +320,19 @@ public function test_explicit_arguments_are_used_when_defined(): void { // Verify arguments_source is 'explicit'. $this->assertSame( 'explicit', $built['adapter_meta']['arguments_source'] ); - } + } - public function test_explicit_arguments_override_input_schema(): void { - $ability = wp_get_ability( 'test/prompt-explicit-args-override' ); - $this->assertNotNull( $ability, 'Ability test/prompt-explicit-args-override should be registered' ); + public function test_explicit_arguments_override_input_schema(): void { + $ability = wp_get_ability( 'test/prompt-explicit-args-override' ); + $this->assertNotNull( $ability, 'Ability test/prompt-explicit-args-override should be registered' ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); // Should have exactly 1 argument from explicit override, NOT from input_schema. $this->assertArrayHasKey( 'arguments', $arr ); @@ -346,28 +346,28 @@ public function test_explicit_arguments_override_input_schema(): void { $this->assertTrue( $arg['required'] ); // Verify NO schema_field (from input_schema). - foreach ( $arr['arguments'] as $argument ) { - $this->assertNotSame( 'schema_field', $argument['name'] ); - } + foreach ( $arr['arguments'] as $argument ) { + $this->assertNotSame( 'schema_field', $argument['name'] ); + } - // Verify arguments_source is 'explicit'. - $this->assertSame( 'explicit', $built['adapter_meta']['arguments_source'] ); + // Verify arguments_source is 'explicit'. + $this->assertSame( 'explicit', $built['adapter_meta']['arguments_source'] ); - // Verify NO transformation metadata (explicit args bypass schema transform). - $this->assertArrayNotHasKey( 'input_schema_transformed', $built['adapter_meta'] ); - } + // Verify NO transformation metadata (explicit args bypass schema transform). + $this->assertArrayNotHasKey( 'input_schema_transformed', $built['adapter_meta'] ); + } - public function test_empty_explicit_arguments_falls_back_to_schema(): void { - $ability = wp_get_ability( 'test/prompt-empty-explicit-args' ); - $this->assertNotNull( $ability, 'Ability test/prompt-empty-explicit-args should be registered' ); + public function test_empty_explicit_arguments_falls_back_to_schema(): void { + $ability = wp_get_ability( 'test/prompt-empty-explicit-args' ); + $this->assertNotNull( $ability, 'Ability test/prompt-empty-explicit-args should be registered' ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); // Should fall back to input_schema since mcp.arguments is empty. $this->assertArrayHasKey( 'arguments', $arr ); @@ -377,11 +377,11 @@ public function test_empty_explicit_arguments_falls_back_to_schema(): void { $arg = $arr['arguments'][0]; $this->assertSame( 'fallback_field', $arg['name'] ); $this->assertSame( 'This should appear because mcp.arguments is empty', $arg['description'] ); - $this->assertTrue( $arg['required'] ); + $this->assertTrue( $arg['required'] ); - // Verify arguments_source is 'schema' (fell back). - $this->assertSame( 'schema', $built['adapter_meta']['arguments_source'] ); - } + // Verify arguments_source is 'schema' (fell back). + $this->assertSame( 'schema', $built['adapter_meta']['arguments_source'] ); + } public function test_invalid_explicit_arguments_missing_name_returns_wp_error(): void { $ability = wp_get_ability( 'test/prompt-invalid-explicit-args-no-name' ); @@ -405,17 +405,17 @@ public function test_invalid_explicit_arguments_not_array_returns_wp_error(): vo $this->assertStringContainsString( 'must be an array', $result->get_error_message() ); } - public function test_explicit_arguments_with_all_fields(): void { - $ability = wp_get_ability( 'test/prompt-explicit-args-all-fields' ); - $this->assertNotNull( $ability, 'Ability test/prompt-explicit-args-all-fields should be registered' ); + public function test_explicit_arguments_with_all_fields(): void { + $ability = wp_get_ability( 'test/prompt-explicit-args-all-fields' ); + $this->assertNotNull( $ability, 'Ability test/prompt-explicit-args-all-fields should be registered' ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); // Should have 2 arguments. $this->assertArrayHasKey( 'arguments', $arr ); @@ -435,36 +435,36 @@ public function test_explicit_arguments_with_all_fields(): void { $this->assertArrayNotHasKey( 'description', $minimal_arg ); $this->assertArrayNotHasKey( 'required', $minimal_arg ); - // Verify arguments_source. - $this->assertSame( 'explicit', $built['adapter_meta']['arguments_source'] ); - } + // Verify arguments_source. + $this->assertSame( 'explicit', $built['adapter_meta']['arguments_source'] ); + } - public function test_arguments_source_tracks_schema_source(): void { - $ability = wp_get_ability( 'test/prompt' ); - $this->assertNotNull( $ability ); + public function test_arguments_source_tracks_schema_source(): void { + $ability = wp_get_ability( 'test/prompt' ); + $this->assertNotNull( $ability ); - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); - $prompt = $built['prompt']; + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertInstanceOf( PromptDto::class, $built['prompt'] ); + $prompt = $built['prompt']; - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); - // Verify arguments_source is 'schema' for auto-converted arguments. - $this->assertSame( 'schema', $built['adapter_meta']['arguments_source'] ); - } + // Verify arguments_source is 'schema' for auto-converted arguments. + $this->assertSame( 'schema', $built['adapter_meta']['arguments_source'] ); + } - public function test_no_arguments_has_no_arguments_source(): void { - $ability = wp_get_ability( 'test/prompt-no-schema' ); - $this->assertNotNull( $ability ); + public function test_no_arguments_has_no_arguments_source(): void { + $ability = wp_get_ability( 'test/prompt-no-schema' ); + $this->assertNotNull( $ability ); - // Verify arguments_source is NOT present when there are no arguments. - $built = RegisterAbilityAsMcpPrompt::build( $ability ); - $this->assertNotWPError( $built ); - $this->assertIsArray( $built ); - $this->assertArrayNotHasKey( 'arguments_source', $built['adapter_meta'] ); - } + // Verify arguments_source is NOT present when there are no arguments. + $built = RegisterAbilityAsMcpPrompt::build( $ability ); + $this->assertNotWPError( $built ); + $this->assertIsArray( $built ); + $this->assertArrayNotHasKey( 'arguments_source', $built['adapter_meta'] ); + } // ========================================================================= // Icons Tests (MCP 2025-11-25) @@ -531,17 +531,17 @@ public function test_prompt_without_icons_has_no_icons_key(): void { // User _meta Passthrough Tests (MCP 2025-11-25) // ========================================================================= - public function test_user_meta_is_passed_through(): void { - $ability = wp_get_ability( 'test/prompt-with-custom-meta' ); - $this->assertNotNull( $ability, 'Ability test/prompt-with-custom-meta should be registered' ); + public function test_user_meta_is_passed_through(): void { + $ability = wp_get_ability( 'test/prompt-with-custom-meta' ); + $this->assertNotNull( $ability, 'Ability test/prompt-with-custom-meta should be registered' ); - $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); - $this->assertNotWPError( $prompt ); + $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); + $this->assertNotWPError( $prompt ); - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); - // Verify _meta is present with user-defined keys. - $this->assertArrayHasKey( '_meta', $arr ); + // Verify _meta is present with user-defined keys. + $this->assertArrayHasKey( '_meta', $arr ); // Verify custom_vendor key is preserved. $this->assertArrayHasKey( 'custom_vendor', $arr['_meta'] ); @@ -549,22 +549,22 @@ public function test_user_meta_is_passed_through(): void { $this->assertTrue( $arr['_meta']['custom_vendor']['feature_flag'] ); $this->assertSame( '1.0', $arr['_meta']['custom_vendor']['version'] ); - // Verify another_vendor key is preserved. - $this->assertArrayHasKey( 'another_vendor', $arr['_meta'] ); - $this->assertSame( 'some-value', $arr['_meta']['another_vendor'] ); - } + // Verify another_vendor key is preserved. + $this->assertArrayHasKey( 'another_vendor', $arr['_meta'] ); + $this->assertSame( 'some-value', $arr['_meta']['another_vendor'] ); + } - public function test_prompt_without_user_meta_has_no_meta_field(): void { - $ability = wp_get_ability( 'test/prompt' ); - $this->assertNotNull( $ability ); + public function test_prompt_without_user_meta_has_no_meta_field(): void { + $ability = wp_get_ability( 'test/prompt' ); + $this->assertNotNull( $ability ); - $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); - $this->assertNotWPError( $prompt ); + $prompt = RegisterAbilityAsMcpPrompt::make( $ability ); + $this->assertNotWPError( $prompt ); - $arr = $prompt->toArray(); + $arr = $prompt->toArray(); - $this->assertArrayNotHasKey( '_meta', $arr ); - } + $this->assertArrayNotHasKey( '_meta', $arr ); + } // ========================================================================= // Combined Icons and _meta Tests @@ -590,7 +590,7 @@ public function test_prompt_with_both_icons_and_meta(): void { $this->assertArrayHasKey( '_meta', $arr ); $this->assertArrayHasKey( 'vendor_info', $arr['_meta'] ); $this->assertSame( 'test-value', $arr['_meta']['vendor_info']['custom_data'] ); - } + } // ========================================================================= // mcp_adapter_prompt_name Filter Tests @@ -629,4 +629,4 @@ public function test_prompt_name_filter_with_invalid_result_returns_wp_error(): remove_filter( 'mcp_adapter_prompt_name', $filter_callback ); } - } +} diff --git a/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php index 6067006a..685f50d4 100644 --- a/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php +++ b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php @@ -10,15 +10,15 @@ final class RegisterAbilityAsMcpResourceTest extends TestCase { - public function test_make_builds_resource_from_ability(): void { - $ability = wp_get_ability( 'test/resource' ); - $this->assertNotNull( $ability, 'Ability test/resource should be registered' ); - $resource = RegisterAbilityAsMcpResource::make( $ability ); - $this->assertInstanceOf( ResourceDto::class, $resource ); - $arr = $resource->toArray(); - $this->assertSame( 'WordPress://local/resource-1', $arr['uri'] ); - $this->assertNull( $resource->get_meta() ); - } + public function test_make_builds_resource_from_ability(): void { + $ability = wp_get_ability( 'test/resource' ); + $this->assertNotNull( $ability, 'Ability test/resource should be registered' ); + $resource = RegisterAbilityAsMcpResource::make( $ability ); + $this->assertInstanceOf( ResourceDto::class, $resource ); + $arr = $resource->toArray(); + $this->assertSame( 'WordPress://local/resource-1', $arr['uri'] ); + $this->assertNull( $resource->get_meta() ); + } public function test_annotations_are_mapped_to_mcp_format(): void { $ability = wp_get_ability( 'test/resource-with-annotations' ); diff --git a/tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php b/tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php index 1ca808db..7356af0f 100644 --- a/tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php +++ b/tests/phpunit/Unit/Servers/DefaultServerFactoryTest.php @@ -74,7 +74,7 @@ public function test_create_discovers_resources_from_abilities(): void { // Check that test/resource ability was discovered and registered // The test/resource ability has mcp.public=true and mcp.type='resource' - $resources = $server->get_resources(); + $resources = $server->get_resources(); $resource_names = array_map( static function ( $resource ) { return $resource->getName(); @@ -102,7 +102,7 @@ public function test_create_discovers_prompts_from_abilities(): void { // Check that test/prompt ability was discovered and registered // The test/prompt ability has mcp.public=true and mcp.type='prompt' - $prompts = $server->get_prompts(); + $prompts = $server->get_prompts(); $prompt_names = array_map( static function ( $prompt ) { return $prompt->getName(); @@ -156,7 +156,7 @@ public function test_create_registers_default_tools(): void { $server = $this->adapter->get_server( 'mcp-adapter-default-server' ); $this->assertNotNull( $server ); - $tools = $server->get_tools(); + $tools = $server->get_tools(); $tool_names = array_map( static function ( $tool ) { return $tool->getName(); @@ -252,4 +252,3 @@ public function test_create_uses_default_observability_handler(): void { $this->assertInstanceOf( NullMcpObservabilityHandler::class, $server->get_observability_handler() ); } } - diff --git a/tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php b/tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php index 01f2e506..be58c130 100644 --- a/tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php +++ b/tests/phpunit/Unit/Tools/RegisterAbilityAsMcpToolTest.php @@ -572,7 +572,7 @@ public function test_invalid_icons_are_filtered_out(): void { $this->assertContains( 'https://example.com/another-valid.svg', $srcs ); } - public function test_custom_meta_is_passed_through(): void { + public function test_custom_meta_is_passed_through(): void { $ability = wp_get_ability( 'test/with-custom-meta' ); $this->assertNotNull( $ability, 'Ability test/with-custom-meta should be registered' ); @@ -593,10 +593,10 @@ public function test_custom_meta_is_passed_through(): void { $this->assertArrayHasKey( 'another_vendor', $meta ); $this->assertSame( 'some-value', $meta['another_vendor'] ); - $this->assertSame( 'test/with-custom-meta', $built['adapter_meta']['ability'] ); - } + $this->assertSame( 'test/with-custom-meta', $built['adapter_meta']['ability'] ); + } - public function test_icons_and_meta_can_coexist(): void { + public function test_icons_and_meta_can_coexist(): void { $ability = wp_get_ability( 'test/with-icons-and-meta' ); $this->assertNotNull( $ability, 'Ability test/with-icons-and-meta should be registered' ); @@ -614,12 +614,12 @@ public function test_icons_and_meta_can_coexist(): void { $this->assertSame( 'https://example.com/combined-icon.png', $arr['icons'][0]['src'] ); $this->assertSame( array( '48x48' ), $arr['icons'][0]['sizes'] ); - // Verify custom _meta exists (internal adapter metadata is not exposed here). - $meta = $tool->get_meta(); - $this->assertArrayHasKey( 'vendor_info', $meta ); - $this->assertSame( 'test-value', $meta['vendor_info']['custom_data'] ); - $this->assertSame( 'test/with-icons-and-meta', $built['adapter_meta']['ability'] ); - } + // Verify custom _meta exists (internal adapter metadata is not exposed here). + $meta = $tool->get_meta(); + $this->assertArrayHasKey( 'vendor_info', $meta ); + $this->assertSame( 'test-value', $meta['vendor_info']['custom_data'] ); + $this->assertSame( 'test/with-icons-and-meta', $built['adapter_meta']['ability'] ); + } public function test_tool_without_icons_has_no_icons_field(): void { // Use an existing ability that doesn't have icons defined. diff --git a/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php b/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php index bdae32ab..49b86e67 100644 --- a/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php +++ b/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php @@ -81,8 +81,8 @@ public function test_construct_without_request_router_creates_router_automatical public function test_construct_with_request_router_uses_provided_router(): void { $properties = $this->build_required_properties(); // Create a context first to get a RequestRouter instance. - $temp_context = new McpTransportContext( $properties ); - $router = $temp_context->request_router; + $temp_context = new McpTransportContext( $properties ); + $router = $temp_context->request_router; $properties['request_router'] = $router; $context = new McpTransportContext( $properties ); @@ -105,8 +105,8 @@ public function test_construct_without_permission_callback_defaults_to_null(): v * Test that transport_permission_callback is assigned when provided. */ public function test_construct_with_permission_callback_assigns_callback(): void { - $properties = $this->build_required_properties(); - $callback = static function () { + $properties = $this->build_required_properties(); + $callback = static function () { return true; }; $properties['transport_permission_callback'] = $callback; @@ -120,9 +120,9 @@ public function test_construct_with_permission_callback_assigns_callback(): void * Test that error_handler is assigned when provided. */ public function test_construct_with_error_handler_assigns_handler(): void { - $properties = $this->build_required_properties(); - $error_handler = new DummyErrorHandler(); - $properties['error_handler'] = $error_handler; + $properties = $this->build_required_properties(); + $error_handler = new DummyErrorHandler(); + $properties['error_handler'] = $error_handler; $context = new McpTransportContext( $properties ); diff --git a/tests/phpunit/Unit/Transport/McpSessionManagerTest.php b/tests/phpunit/Unit/Transport/McpSessionManagerTest.php index d88ab43b..60df6b3d 100644 --- a/tests/phpunit/Unit/Transport/McpSessionManagerTest.php +++ b/tests/phpunit/Unit/Transport/McpSessionManagerTest.php @@ -333,7 +333,7 @@ public function test_validation_skips_last_activity_update_within_throttle_windo $this->assertIsString( $session_id ); // Record the last_activity right after creation - $sessions_before = SessionManager::get_all_user_sessions( $this->test_user_id ); + $sessions_before = SessionManager::get_all_user_sessions( $this->test_user_id ); $original_activity = $sessions_before[ $session_id ]['last_activity']; // Validate immediately (within the 60s throttle window) @@ -356,7 +356,7 @@ public function test_validation_does_not_call_cleanup(): void { $this->assertIsString( $expired_session_id ); // Backdate one session to make it expired - $sessions = SessionManager::get_all_user_sessions( $this->test_user_id ); + $sessions = SessionManager::get_all_user_sessions( $this->test_user_id ); $sessions[ $expired_session_id ]['last_activity'] = time() - ( DAY_IN_SECONDS + 3600 ); update_user_meta( $this->test_user_id, 'mcp_adapter_sessions', $sessions ); From 66638bed69601f3ff43a84f8a2820ae342651d46 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Mon, 18 May 2026 00:01:33 +0300 Subject: [PATCH 4/6] fix: call class method instead of trait. --- includes/Transport/Infrastructure/RequestRouter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/Transport/Infrastructure/RequestRouter.php b/includes/Transport/Infrastructure/RequestRouter.php index 95ddbb45..255b65f1 100644 --- a/includes/Transport/Infrastructure/RequestRouter.php +++ b/includes/Transport/Infrastructure/RequestRouter.php @@ -10,7 +10,7 @@ namespace WP\MCP\Transport\Infrastructure; use WP\MCP\Infrastructure\ErrorHandling\McpErrorFactory; -use WP\MCP\Infrastructure\Observability\McpObservabilityHelperTrait; +use WP\MCP\Infrastructure\Observability\ErrorLogMcpObservabilityHandler; use WP\McpSchema\Common\AbstractDataTransferObject; use WP\McpSchema\Common\Content\DTO\TextContent; use WP\McpSchema\Common\JsonRpc\DTO\JSONRPCErrorResponse; @@ -289,7 +289,7 @@ private function sanitize_params_for_logging( array $params ): array { // Filter argument keys to exclude sensitive-looking ones. $safe_keys = array(); foreach ( array_keys( $params['arguments'] ) as $arg_key ) { - if ( McpObservabilityHelperTrait::is_sensitive_key( (string) $arg_key ) ) { + if ( ErrorLogMcpObservabilityHandler::is_sensitive_key( (string) $arg_key ) ) { $safe_keys[] = '[REDACTED]'; } else { $safe_keys[] = $arg_key; From 9afa2f23e7255d8e7902c0dde23982a3b3a6d004 Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Mon, 18 May 2026 00:03:40 +0300 Subject: [PATCH 5/6] tests: deslop and test for explicit `_doing_it_wrong()`s --- tests/phpunit/Fixtures/DummyAbility.php | 158 ------------------ .../BuilderPromptExecutionTest.php | 1 + .../phpunit/Integration/HttpTransportTest.php | 2 +- tests/phpunit/TestCase.php | 93 +++-------- .../DiscoverAbilitiesAbilityTest.php | 27 +-- .../Abilities/ExecuteAbilityAbilityTest.php | 31 ++-- .../Abilities/GetAbilityInfoAbilityTest.php | 24 +-- .../Unit/Core/McpAdapterConfigTest.php | 82 ++++++++- .../Unit/Core/McpAdapterErrorHandlingTest.php | 2 + .../Unit/Core/McpComponentRegistryTest.php | 3 + .../Unit/Core/McpTransportFactoryTest.php | 3 + .../Unit/Handlers/ResourcesHandlerTest.php | 4 +- .../Unit/Prompts/McpPromptBuilderTest.php | 1 + .../RegisterAbilityAsMcpResourceTest.php | 148 +++++++++++++++- .../McpTransportContextTest.php | 4 +- 15 files changed, 276 insertions(+), 307 deletions(-) diff --git a/tests/phpunit/Fixtures/DummyAbility.php b/tests/phpunit/Fixtures/DummyAbility.php index 4129575d..ee978fa7 100644 --- a/tests/phpunit/Fixtures/DummyAbility.php +++ b/tests/phpunit/Fixtures/DummyAbility.php @@ -266,25 +266,6 @@ public static function register_abilities(): void { ) ); - // Resource ability with extra whitespace around URI for normalization tests - wp_register_ability( - 'test/resource-whitespace-uri', - array( - 'label' => 'Resource With Whitespace URI', - 'description' => 'Resource whose URI includes leading/trailing spaces', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'uri' => ' WordPress://local/resource-whitespace ', - ), - ) - ); - // Prompt ability with arguments wp_register_ability( 'test/prompt', @@ -458,89 +439,6 @@ public static function register_abilities(): void { ) ); - // Resource with annotations - wp_register_ability( - 'test/resource-with-annotations', - array( - 'label' => 'Resource With Annotations', - 'description' => 'A resource with MCP annotations', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'uri' => 'WordPress://local/resource-annotated', - 'annotations' => array( - 'audience' => array( 'user', 'assistant' ), - 'lastModified' => '2024-01-15T10:30:00Z', - 'priority' => 0.8, - ), - 'mcp' => array( - 'public' => true, - 'type' => 'resource', - ), - ), - ) - ); - - // Resource with partial annotations - wp_register_ability( - 'test/resource-partial-annotations', - array( - 'label' => 'Resource Partial Annotations', - 'description' => 'A resource with only some annotations', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'uri' => 'WordPress://local/resource-partial', - 'annotations' => array( - 'priority' => 0.5, - ), - 'mcp' => array( - 'public' => true, - 'type' => 'resource', - ), - ), - ) - ); - - // Resource with invalid annotations (should be filtered) - wp_register_ability( - 'test/resource-invalid-annotations', - array( - 'label' => 'Resource Invalid Annotations', - 'description' => 'A resource with invalid annotations', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'uri' => 'WordPress://local/resource-invalid', - 'annotations' => array( - 'audience' => array( 'invalid-role' ), // Invalid role - 'lastModified' => 'not-a-date', // Invalid date - 'priority' => 2.0, // Out of range - 'invalidField' => 'should-be-filtered', // Unknown field - ), - 'mcp' => array( - 'public' => true, - 'type' => 'resource', - ), - ), - ) - ); - // Prompt with annotations wp_register_ability( 'test/prompt-with-annotations', @@ -1063,62 +961,6 @@ public static function register_abilities(): void { ) ); - // Resource with INVALID annotations in new meta structure (for validation testing) - // All annotations should be dropped with _doing_it_wrong notice - wp_register_ability( - 'test/resource-invalid-annotations-new-meta', - array( - 'label' => 'Resource Invalid Annotations New Meta', - 'description' => 'A resource with invalid annotations using new meta structure', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'mcp' => array( - 'public' => true, - 'type' => 'resource', - 'uri' => 'WordPress://local/resource-invalid-annotations-new', - 'annotations' => array( - 'audience' => array( 'admin', 'superuser' ), // Invalid roles (should be 'user' or 'assistant') - 'lastModified' => 'yesterday', // Invalid ISO 8601 timestamp - 'priority' => 2.5, // Out of range (should be 0.0-1.0) - ), - ), - ), - ) - ); - - // Resource with MIXED valid/invalid annotations - should drop ALL because one is invalid - wp_register_ability( - 'test/resource-mixed-annotations', - array( - 'label' => 'Resource Mixed Annotations', - 'description' => 'A resource with one valid and one invalid annotation', - 'category' => 'test', - 'execute_callback' => static function () { - return 'content'; - }, - 'permission_callback' => static function () { - return true; - }, - 'meta' => array( - 'mcp' => array( - 'public' => true, - 'type' => 'resource', - 'uri' => 'WordPress://local/resource-mixed-annotations', - 'annotations' => array( - 'priority' => 0.5, // Valid - 'lastModified' => 'not-valid-timestamp', // Invalid - should cause ALL to be dropped - ), - ), - ), - ) - ); - // Resource with icons (using new meta structure) wp_register_ability( 'test/resource-with-icons', diff --git a/tests/phpunit/Integration/BuilderPromptExecutionTest.php b/tests/phpunit/Integration/BuilderPromptExecutionTest.php index ac2ef148..577b0ba4 100644 --- a/tests/phpunit/Integration/BuilderPromptExecutionTest.php +++ b/tests/phpunit/Integration/BuilderPromptExecutionTest.php @@ -111,6 +111,7 @@ public function test_builder_prompt_permission_denied(): void { public function test_mixed_ability_and_builder_prompts(): void { // Register both builder and ability-based prompts + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $server = $this->makeServer( array(), array(), diff --git a/tests/phpunit/Integration/HttpTransportTest.php b/tests/phpunit/Integration/HttpTransportTest.php index 48bb29f7..208b01cc 100644 --- a/tests/phpunit/Integration/HttpTransportTest.php +++ b/tests/phpunit/Integration/HttpTransportTest.php @@ -62,7 +62,7 @@ public function set_up(): void { array( HttpTransport::class ), DummyErrorHandler::class, DummyObservabilityHandler::class, - array( 'test/tool' ), + array( 'test/always-allowed' ), array( 'test/resource' ), array( 'test/prompt' ) ); diff --git a/tests/phpunit/TestCase.php b/tests/phpunit/TestCase.php index 9477bfc0..ad2ec54d 100644 --- a/tests/phpunit/TestCase.php +++ b/tests/phpunit/TestCase.php @@ -9,6 +9,7 @@ namespace WP\MCP\Tests; +use WP\MCP\Core\McpAdapter; use WP\MCP\Core\McpServer; use WP\MCP\Tests\Fixtures\DummyAbility; use WP\MCP\Tests\Fixtures\DummyErrorHandler; @@ -35,52 +36,24 @@ abstract class TestCase extends WP_UnitTestCase { public static function set_up_before_class(): void { parent::set_up_before_class(); - // Register mcp-adapter category during the proper hook - add_action( - 'wp_abilities_api_categories_init', - static function () { - $categories_registry = \WP_Ability_Categories_Registry::get_instance(); - if ( $categories_registry->is_registered( 'mcp-adapter' ) ) { - return; - } + // Register plugin's default category and abilities via the same methods + // the production code uses. We hook them the same way McpAdapter::maybe_create_default_server() + // does, so if the hooks haven't fired yet they'll be picked up automatically. + $adapter = McpAdapter::instance(); + add_action( 'wp_abilities_api_categories_init', array( $adapter, 'register_default_category' ) ); + add_action( 'wp_abilities_api_init', array( $adapter, 'register_default_abilities' ) ); - wp_register_ability_category( - 'mcp-adapter', - array( - 'label' => 'MCP Adapter', - 'description' => 'Abilities for the MCP Adapter', - ) - ); - } - ); - - // Use DummyAbility to register test category + // Use DummyAbility to register test category and abilities. add_action( 'wp_abilities_api_categories_init', array( DummyAbility::class, 'register_category' ) ); - - // Use DummyAbility to register test abilities add_action( 'wp_abilities_api_init', array( DummyAbility::class, 'register_abilities' ) ); } - /** - * Set up before each test. - * - * Sets up `_doing_it_wrong` capturing for all tests. - */ - public function set_up(): void { - parent::set_up(); - $this->doing_it_wrong_log = array(); - add_action( 'doing_it_wrong_run', array( $this, 'record_doing_it_wrong' ), 10, 3 ); - } - /** * Clean up after each test. * - * This method resets the state of test handlers to ensure test isolation. - * Automatically resets DummyErrorHandler and DummyObservabilityHandler between tests. + * Resets DummyErrorHandler and DummyObservabilityHandler between tests. */ public function tear_down(): void { - remove_action( 'doing_it_wrong_run', array( $this, 'record_doing_it_wrong' ) ); - $this->doing_it_wrong_log = array(); DummyErrorHandler::reset(); DummyObservabilityHandler::reset(); parent::tear_down(); @@ -113,30 +86,6 @@ public function makeServer( array $tools = array(), array $resources = array(), ); } - /** - * Captured `_doing_it_wrong` calls during a test. - * - * @var list - */ - protected $doing_it_wrong_log = array(); - - /** - * Records `_doing_it_wrong` calls for later assertions. - * - * @param string $the_method Function name flagged by `_doing_it_wrong`. - * @param string $message Message supplied to `_doing_it_wrong`. - * @param string $version Version string supplied to `_doing_it_wrong`. - * - * @return void - */ - public function record_doing_it_wrong( string $the_method, string $message, string $version ): void { - $this->doing_it_wrong_log[] = array( - 'function' => $the_method, - 'message' => $message, - 'version' => $version, - ); - } - /** * Registers an ability inside the wp_abilities_api_init hook. * @@ -149,23 +98,21 @@ public function record_doing_it_wrong( string $the_method, string $message, stri * @return void */ protected function register_ability_in_hook( string $name, array $args ): void { - // If we're already inside the hook, register directly - if ( doing_action( 'wp_abilities_api_init' ) ) { - wp_register_ability( $name, $args ); + // If already registered, skip to avoid duplicate-registration _doing_it_wrong. + if ( wp_has_ability( $name ) ) { return; } - // Create a callback that registers the ability - $callback = static function () use ( $name, $args ) { + // If we're already inside the hook, register directly. + if ( doing_action( 'wp_abilities_api_init' ) ) { wp_register_ability( $name, $args ); - }; - - // Add the callback to the hook - add_action( 'wp_abilities_api_init', $callback, 999 ); - - do_action( 'wp_abilities_api_init' ); + return; + } - // Clean up the callback to prevent duplicate registrations if hook fires again - remove_action( 'wp_abilities_api_init', $callback, 999 ); + // Spoof hook context to register ability without triggering _doing_it_wrong. + global $wp_current_filter; + $wp_current_filter[] = 'wp_abilities_api_init'; + wp_register_ability( $name, $args ); + array_pop( $wp_current_filter ); } } diff --git a/tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php b/tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php index 457db926..abe84fc8 100644 --- a/tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php +++ b/tests/phpunit/Unit/Abilities/DiscoverAbilitiesAbilityTest.php @@ -23,13 +23,12 @@ final class DiscoverAbilitiesAbilityTest extends TestCase { * * @var int */ - private static $user_id; - - public static function set_up_before_class(): void { - parent::set_up_before_class(); + private int $user_id; + public function set_up(): void { + parent::set_up(); // Create a test user for authentication tests - self::$user_id = wp_insert_user( + $this->user_id = self::factory()->user->create( array( 'user_login' => 'testuser', 'user_pass' => 'testpass', @@ -37,25 +36,13 @@ public static function set_up_before_class(): void { 'role' => 'administrator', ) ); - } - - public static function tear_down_after_class(): void { - // Clean up test user - if ( self::$user_id ) { - wp_delete_user( self::$user_id ); - } - parent::tear_down_after_class(); - } - - public function set_up(): void { - parent::set_up(); - // Set current user for each test - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function tear_down(): void { // Reset current user after each test wp_set_current_user( 0 ); + wp_delete_user( $this->user_id ); parent::tear_down(); } @@ -148,7 +135,7 @@ public function test_check_permission_requires_capability(): void { // Clean up wp_delete_user( $limited_user_id ); - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function test_execute_returns_abilities_list(): void { diff --git a/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php b/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php index 81eb0307..be1be906 100644 --- a/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php +++ b/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php @@ -23,13 +23,11 @@ final class ExecuteAbilityAbilityTest extends TestCase { * * @var int */ - private static $user_id; + private $user_id; - public static function set_up_before_class(): void { - parent::set_up_before_class(); - - // Create a test user for authentication tests - self::$user_id = wp_insert_user( + public function set_up(): void { + parent::set_up(); + $this->user_id = $this->factory()->user->create( array( 'user_login' => 'testuser', 'user_pass' => 'testpass', @@ -37,20 +35,8 @@ public static function set_up_before_class(): void { 'role' => 'administrator', ) ); - } - - public static function tear_down_after_class(): void { - // Clean up test user - if ( self::$user_id ) { - wp_delete_user( self::$user_id ); - } - parent::tear_down_after_class(); - } - - public function set_up(): void { - parent::set_up(); // Set current user for each test - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function tear_down(): void { @@ -115,6 +101,7 @@ public function test_check_permission_with_empty_ability_name(): void { } public function test_check_permission_with_nonexistent_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $result = ExecuteAbilityAbility::check_permission( array( 'ability_name' => 'nonexistent/ability', @@ -178,7 +165,7 @@ public function test_check_permission_requires_authentication(): void { $this->assertEquals( 'authentication_required', $result->get_error_code() ); // Restore authenticated user for other tests - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function test_check_permission_requires_capability(): void { @@ -210,7 +197,7 @@ public function test_check_permission_requires_capability(): void { // Clean up wp_delete_user( $limited_user_id ); - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function test_check_permission_with_public_mcp_metadata(): void { @@ -254,6 +241,7 @@ public function test_check_permission_with_public_mcp_metadata(): void { public function test_check_permission_with_nonexistent_ability_for_mcp_check(): void { // Test with an ability that doesn't exist (should fail at MCP exposure check) + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $result = ExecuteAbilityAbility::check_permission( array( 'ability_name' => 'nonexistent/test-ability', @@ -315,6 +303,7 @@ public function test_execute_with_empty_ability_name(): void { } public function test_execute_with_nonexistent_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $result = ExecuteAbilityAbility::execute( array( 'ability_name' => 'nonexistent/ability', diff --git a/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php b/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php index 7c919980..f5ef0358 100644 --- a/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php +++ b/tests/phpunit/Unit/Abilities/GetAbilityInfoAbilityTest.php @@ -23,13 +23,13 @@ final class GetAbilityInfoAbilityTest extends TestCase { * * @var int */ - private static $user_id; + private int $user_id; - public static function set_up_before_class(): void { - parent::set_up_before_class(); + public function set_up(): void { + parent::set_up(); // Create a test user for authentication tests - self::$user_id = wp_insert_user( + $this->user_id = self::factory()->user->create( array( 'user_login' => 'testuser', 'user_pass' => 'testpass', @@ -37,20 +37,9 @@ public static function set_up_before_class(): void { 'role' => 'administrator', ) ); - } - - public static function tear_down_after_class(): void { - // Clean up test user - if ( self::$user_id ) { - wp_delete_user( self::$user_id ); - } - parent::tear_down_after_class(); - } - public function set_up(): void { - parent::set_up(); // Set current user for each test - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function tear_down(): void { @@ -151,7 +140,7 @@ public function test_check_permission_requires_capability(): void { // Clean up wp_delete_user( $limited_user_id ); - wp_set_current_user( self::$user_id ); + wp_set_current_user( $this->user_id ); } public function test_check_permission_with_missing_ability_name(): void { @@ -249,6 +238,7 @@ public function test_execute_with_empty_ability_name(): void { } public function test_execute_with_nonexistent_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $result = GetAbilityInfoAbility::execute( array( 'ability_name' => 'nonexistent/ability', diff --git a/tests/phpunit/Unit/Core/McpAdapterConfigTest.php b/tests/phpunit/Unit/Core/McpAdapterConfigTest.php index cd0c99d7..2f8bc8a7 100644 --- a/tests/phpunit/Unit/Core/McpAdapterConfigTest.php +++ b/tests/phpunit/Unit/Core/McpAdapterConfigTest.php @@ -249,15 +249,10 @@ static function ( $defaults ) use ( &$received_config ) { $this->assertSame( array( 'test/resource', - 'test/resource-with-annotations', - 'test/resource-partial-annotations', - 'test/resource-invalid-annotations', 'test/resource-new-meta', 'test/resource-invalid-uri', 'test/resource-invalid-mimetype', 'test/resource-with-size', - 'test/resource-invalid-annotations-new-meta', - 'test/resource-mixed-annotations', 'test/resource-with-icons', 'test/resource-missing-uri', 'test/resource-valid-mimetype', @@ -336,6 +331,83 @@ static function ( $defaults ) { $this->assertSame( 'v3.0.0', $server->get_server_version() ); } + public function test_discovery_includes_old_meta_resource_but_excludes_non_public(): void { + // Register an old-meta resource (uri at meta top-level, mcp.public=true). + $this->register_ability_in_hook( + 'test/resource-old-meta-discoverable', + array( + 'label' => 'Old Meta Discoverable Resource', + 'description' => 'Resource using old meta format with mcp.public=true', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'uri' => 'WordPress://local/resource-old-meta', + 'mcp' => array( + 'public' => true, + 'type' => 'resource', + ), + ), + ) + ); + + // Register a non-public resource (mcp.public missing entirely). + $this->register_ability_in_hook( + 'test/resource-non-public', + array( + 'label' => 'Non-Public Resource', + 'description' => 'Resource without mcp.public flag', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'mcp' => array( + 'type' => 'resource', + ), + ), + ) + ); + + $received_config = null; + + add_filter( + 'mcp_adapter_default_server_config', + static function ( $defaults ) use ( &$received_config ) { + $received_config = $defaults; + return $defaults; + } + ); + + // Mock being inside mcp_adapter_init. + global $wp_current_filter; + $wp_current_filter[] = 'mcp_adapter_init'; + + // Old-meta 'uri' key triggers a deprecation notice during resource conversion. + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_mcp_meta' ); + $this->adapter->init(); + + array_pop( $wp_current_filter ); + + $this->assertNotNull( $received_config ); + + // Old-meta resource should be discovered (has mcp.public=true, mcp.type=resource). + $this->assertContains( 'test/resource-old-meta-discoverable', $received_config['resources'] ); + // Non-public resource should NOT be discovered (missing mcp.public). + $this->assertNotContains( 'test/resource-non-public', $received_config['resources'] ); + + // Cleanup. + wp_unregister_ability( 'test/resource-old-meta-discoverable' ); + wp_unregister_ability( 'test/resource-non-public' ); + } + public function test_default_server_factory_handles_wp_error_from_create_server(): void { // Configure default server with invalid error handler to force WP_Error add_filter( diff --git a/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php b/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php index dd83da58..d10cd98b 100644 --- a/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php +++ b/tests/phpunit/Unit/Core/McpAdapterErrorHandlingTest.php @@ -146,6 +146,7 @@ public function test_create_server_returns_wp_error_when_observability_handler_d public function test_create_server_returns_wp_error_when_called_outside_mcp_adapter_init(): void { // Don't mock being inside mcp_adapter_init - call it directly + $this->setExpectedIncorrectUsage( 'create_server' ); $result = $this->adapter->create_server( 'test-server', 'mcp/v1', @@ -185,6 +186,7 @@ public function test_create_server_returns_wp_error_for_duplicate_server_id(): v $this->assertNotWPError( $first_result ); // Try to create second server with same ID + $this->setExpectedIncorrectUsage( 'create_server' ); $second_result = $this->adapter->create_server( 'duplicate-id', 'mcp/v1', diff --git a/tests/phpunit/Unit/Core/McpComponentRegistryTest.php b/tests/phpunit/Unit/Core/McpComponentRegistryTest.php index 27917577..aa5f75b0 100644 --- a/tests/phpunit/Unit/Core/McpComponentRegistryTest.php +++ b/tests/phpunit/Unit/Core/McpComponentRegistryTest.php @@ -132,6 +132,7 @@ static function ( $event ) { } public function test_register_tools_with_invalid_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $this->registry->register_tools( array( 'nonexistent/ability' ) ); $tools = $this->registry->get_tools(); @@ -216,6 +217,7 @@ static function ( $event ) { } public function test_register_resources_with_invalid_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $this->registry->register_resources( array( 'nonexistent/resource' ) ); $resources = $this->registry->get_resources(); @@ -300,6 +302,7 @@ static function ( $event ) { } public function test_register_prompts_with_invalid_ability(): void { + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $this->registry->register_prompts( array( 'nonexistent/prompt' ) ); $prompts = $this->registry->get_prompts(); diff --git a/tests/phpunit/Unit/Core/McpTransportFactoryTest.php b/tests/phpunit/Unit/Core/McpTransportFactoryTest.php index cea24512..68e79627 100644 --- a/tests/phpunit/Unit/Core/McpTransportFactoryTest.php +++ b/tests/phpunit/Unit/Core/McpTransportFactoryTest.php @@ -84,6 +84,7 @@ public function test_initialize_transports_with_valid_transport(): void { public function test_initialize_transports_with_nonexistent_class(): void { // This should trigger _doing_it_wrong but not throw exception + $this->setExpectedIncorrectUsage( 'initialize_transports' ); $this->transport_factory->initialize_transports( array( 'NonExistentTransportClass' ) ); // If we get here without exception, the method handled the nonexistent class gracefully @@ -93,6 +94,7 @@ public function test_initialize_transports_with_nonexistent_class(): void { public function test_initialize_transports_with_invalid_interface(): void { // This should trigger _doing_it_wrong but not throw exception // The method logs the error and continues processing other transports + $this->setExpectedIncorrectUsage( 'initialize_transports' ); $this->transport_factory->initialize_transports( array( \stdClass::class ) ); // If we get here without exception, the method handled the invalid interface gracefully @@ -114,6 +116,7 @@ public function test_initialize_transports_with_multiple_transports(): void { public function test_initialize_transports_with_mixed_validity(): void { // Mix valid and invalid transports + $this->setExpectedIncorrectUsage( 'initialize_transports' ); $this->transport_factory->initialize_transports( array( 'NonExistentClass', diff --git a/tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php b/tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php index 24a7721e..b47f2788 100644 --- a/tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php +++ b/tests/phpunit/Unit/Handlers/ResourcesHandlerTest.php @@ -150,10 +150,10 @@ public function test_read_resource_with_wp_error_from_execute(): void { return true; }, 'meta' => array( - 'uri' => 'WordPress://test/wp-error-resource', 'mcp' => array( 'public' => true, 'type' => 'resource', + 'uri' => 'WordPress://test/wp-error-resource', ), ), ) @@ -201,10 +201,10 @@ public function test_read_resource_with_exception(): void { return true; }, 'meta' => array( - 'uri' => 'WordPress://test/resource-exception', 'mcp' => array( 'public' => true, 'type' => 'resource', + 'uri' => 'WordPress://test/resource-exception', ), ), ) diff --git a/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php b/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php index 9e02739e..479d1410 100644 --- a/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php +++ b/tests/phpunit/Unit/Prompts/McpPromptBuilderTest.php @@ -221,6 +221,7 @@ public function test_prompt_execution_bypasses_abilities(): void { public function test_mixed_registration_abilities_and_builders(): void { // This should work with mixed registration (though abilities won't exist in test) + $this->setExpectedIncorrectUsage( 'WP_Abilities_Registry::get_registered' ); $server = $this->makeServer( array(), array(), diff --git a/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php index 685f50d4..82f2b248 100644 --- a/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php +++ b/tests/phpunit/Unit/Resources/RegisterAbilityAsMcpResourceTest.php @@ -21,9 +21,36 @@ public function test_make_builds_resource_from_ability(): void { } public function test_annotations_are_mapped_to_mcp_format(): void { + $this->register_ability_in_hook( + 'test/resource-with-annotations', + array( + 'label' => 'Resource With Annotations', + 'description' => 'A resource with MCP annotations', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'uri' => 'WordPress://local/resource-annotated', + 'annotations' => array( + 'audience' => array( 'user', 'assistant' ), + 'lastModified' => '2024-01-15T10:30:00Z', + 'priority' => 0.8, + ), + 'mcp' => array( + 'public' => true, + 'type' => 'resource', + ), + ), + ) + ); + $ability = wp_get_ability( 'test/resource-with-annotations' ); - $this->assertNotNull( $ability, 'Ability test/resource-with-annotations should be registered' ); + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_mcp_meta' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); $this->assertNotWPError( $resource ); @@ -41,12 +68,40 @@ public function test_annotations_are_mapped_to_mcp_format(): void { $this->assertContains( 'assistant', $arr['annotations']['audience'] ); $this->assertSame( '2024-01-15T10:30:00Z', $arr['annotations']['lastModified'] ); $this->assertSame( 0.8, $arr['annotations']['priority'] ); + + // Cleanup. + wp_unregister_ability( 'test/resource-with-annotations' ); } public function test_partial_annotations_are_included(): void { + $this->register_ability_in_hook( + 'test/resource-partial-annotations', + array( + 'label' => 'Resource Partial Annotations', + 'description' => 'A resource with only some annotations', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'uri' => 'WordPress://local/resource-partial', + 'annotations' => array( + 'priority' => 0.5, + ), + 'mcp' => array( + 'public' => true, + 'type' => 'resource', + ), + ), + ) + ); + $ability = wp_get_ability( 'test/resource-partial-annotations' ); - $this->assertNotNull( $ability, 'Ability test/resource-partial-annotations should be registered' ); + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_mcp_meta' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); $this->assertNotWPError( $resource ); @@ -58,6 +113,9 @@ public function test_partial_annotations_are_included(): void { $this->assertSame( 0.5, $arr['annotations']['priority'] ); $this->assertArrayNotHasKey( 'audience', $arr['annotations'] ); $this->assertArrayNotHasKey( 'lastModified', $arr['annotations'] ); + + // Cleanup. + wp_unregister_ability( 'test/resource-partial-annotations' ); } public function test_empty_annotations_are_not_included(): void { @@ -74,14 +132,35 @@ public function test_empty_annotations_are_not_included(): void { } public function test_get_uri_trims_whitespace_from_meta(): void { + $this->register_ability_in_hook( + 'test/resource-whitespace-uri', + array( + 'label' => 'Resource With Whitespace URI', + 'description' => 'Resource whose URI includes leading/trailing spaces', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'uri' => ' WordPress://local/resource-whitespace ', + ), + ) + ); + $ability = wp_get_ability( 'test/resource-whitespace-uri' ); - $this->assertNotNull( $ability, 'Ability test/resource-whitespace-uri should be registered' ); + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_mcp_meta' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); $this->assertNotWPError( $resource ); $arr = $resource->toArray(); $this->assertSame( 'WordPress://local/resource-whitespace', $arr['uri'] ); + + // Cleanup. + wp_unregister_ability( 'test/resource-whitespace-uri' ); } public function test_new_meta_structure_maps_all_fields(): void { @@ -232,8 +311,34 @@ public function test_invalid_uri_filter_result_returns_wp_error(): void { } public function test_invalid_annotations_are_dropped_with_doing_it_wrong(): void { + $this->register_ability_in_hook( + 'test/resource-invalid-annotations-new-meta', + array( + 'label' => 'Resource Invalid Annotations New Meta', + 'description' => 'A resource with invalid annotations using new meta structure', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'mcp' => array( + 'public' => true, + 'type' => 'resource', + 'uri' => 'WordPress://local/resource-invalid-annotations-new', + 'annotations' => array( + 'audience' => array( 'admin', 'superuser' ), // Invalid roles (should be 'user' or 'assistant') + 'lastModified' => 'yesterday', // Invalid ISO 8601 timestamp + 'priority' => 2.5, // Out of range (should be 0.0-1.0) + ), + ), + ), + ) + ); + $ability = wp_get_ability( 'test/resource-invalid-annotations-new-meta' ); - $this->assertNotNull( $ability, 'Ability test/resource-invalid-annotations-new-meta should be registered' ); $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_data' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); @@ -245,12 +350,41 @@ public function test_invalid_annotations_are_dropped_with_doing_it_wrong(): void // Annotations should NOT be present (all dropped due to validation errors). $this->assertArrayNotHasKey( 'annotations', $arr ); + + // Cleanup. + wp_unregister_ability( 'test/resource-invalid-annotations-new-meta' ); } public function test_mixed_valid_invalid_annotations_drops_all(): void { + $this->register_ability_in_hook( + 'test/resource-mixed-annotations', + array( + 'label' => 'Resource Mixed Annotations', + 'description' => 'A resource with one valid and one invalid annotation', + 'category' => 'test', + 'execute_callback' => static function () { + return 'content'; + }, + 'permission_callback' => static function () { + return true; + }, + 'meta' => array( + 'mcp' => array( + 'public' => true, + 'type' => 'resource', + 'uri' => 'WordPress://local/resource-mixed-annotations', + 'annotations' => array( + 'priority' => 0.5, // Valid + 'lastModified' => 'not-valid-timestamp', // Invalid - should cause ALL to be dropped + ), + ), + ), + ) + ); + $ability = wp_get_ability( 'test/resource-mixed-annotations' ); - $this->assertNotNull( $ability, 'Ability test/resource-mixed-annotations should be registered' ); + $this->setExpectedIncorrectUsage( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_data' ); $resource = RegisterAbilityAsMcpResource::make( $ability ); // Resource should still be created successfully. @@ -262,8 +396,8 @@ public function test_mixed_valid_invalid_annotations_drops_all(): void { // This is because we drop all if ANY are invalid. $this->assertArrayNotHasKey( 'annotations', $arr ); - // Verify _doing_it_wrong was triggered. - $this->assertDoingItWrongTriggered( 'WP\MCP\Domain\Resources\RegisterAbilityAsMcpResource::get_data' ); + // Cleanup. + wp_unregister_ability( 'test/resource-mixed-annotations' ); } public function test_valid_annotations_are_preserved(): void { diff --git a/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php b/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php index 49b86e67..6af33f7c 100644 --- a/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php +++ b/tests/phpunit/Unit/Transport/Infrastructure/McpTransportContextTest.php @@ -41,9 +41,7 @@ final class McpTransportContextTest extends TestCase { */ public function set_up(): void { parent::set_up(); - $this->server = $this->makeServer( - array( 'test-dummy/echo-tool' ), - ); + $this->server = $this->makeServer(); } /** From a17e03e89225c76cab375bfa6747438a725d910f Mon Sep 17 00:00:00 2001 From: Dovid Levine Date: Mon, 18 May 2026 00:32:55 +0300 Subject: [PATCH 6/6] tests: fix compatibility with 7.0 exceptions --- tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php b/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php index be1be906..739c6f95 100644 --- a/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php +++ b/tests/phpunit/Unit/Abilities/ExecuteAbilityAbilityTest.php @@ -389,7 +389,7 @@ public function test_execute_with_ability_throwing_exception(): void { $this->assertArrayHasKey( 'success', $result ); $this->assertArrayHasKey( 'error', $result ); $this->assertFalse( $result['success'] ); - $this->assertEquals( 'Test execution exception', $result['error'] ); + $this->assertStringContainsString( 'Test execution exception', $result['error'] ); // Clean up wp_unregister_ability( 'test/exception-execution' );