diff --git a/activitypub.php b/activitypub.php index ee61f22b6..29efcec29 100644 --- a/activitypub.php +++ b/activitypub.php @@ -44,12 +44,12 @@ function rest_init() { ( new Rest\Inbox() )->register_routes(); ( new Rest\Followers() )->register_routes(); ( new Rest\Following() )->register_routes(); - ( new Rest\Webfinger() )->register_routes(); ( new Rest\Comment() )->register_routes(); ( new Rest\Server() )->register_routes(); ( new Rest\Collection() )->register_routes(); ( new Rest\Interaction() )->register_routes(); ( new Rest\Post() )->register_routes(); + ( new Rest\Webfinger_Controller() )->register_routes(); // Load NodeInfo endpoints only if blog is public. if ( is_blog_public() ) { diff --git a/includes/rest/class-webfinger.php b/includes/rest/class-webfinger-controller.php similarity index 89% rename from includes/rest/class-webfinger.php rename to includes/rest/class-webfinger-controller.php index 33bb52f65..c9026b9b6 100644 --- a/includes/rest/class-webfinger.php +++ b/includes/rest/class-webfinger-controller.php @@ -14,7 +14,7 @@ * * @see https://webfinger.net/ */ -class Webfinger extends \WP_REST_Controller { +class Webfinger_Controller extends \WP_REST_Controller { /** * The namespace of this controller's route. * @@ -22,17 +22,25 @@ class Webfinger extends \WP_REST_Controller { */ protected $namespace = ACTIVITYPUB_REST_NAMESPACE; + /** + * The base of this controller's route. + * + * @var string + */ + protected $rest_base = 'webfinger'; + /** * Register routes. */ public function register_routes() { \register_rest_route( $this->namespace, - '/webfinger', + '/' . $this->rest_base, array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), + 'permission_callback' => '__return_true', 'args' => array( 'resource' => array( 'description' => 'The WebFinger resource.', @@ -41,7 +49,6 @@ public function register_routes() { 'pattern' => '^(acct:)|^(https?://)(.+)$', ), ), - 'permission_callback' => '__return_true', ), 'schema' => array( $this, 'get_item_schema' ), ) @@ -115,6 +122,7 @@ public function get_item_schema() { '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'webfinger', 'type' => 'object', + 'required' => array( 'subject', 'links' ), 'properties' => array( 'subject' => array( 'description' => 'The subject of this WebFinger record.', @@ -138,6 +146,7 @@ public function get_item_schema() { 'rel' => array( 'description' => 'The relation type of the link.', 'type' => 'string', + 'required' => true, ), 'type' => array( 'description' => 'The content type of the link.', @@ -148,6 +157,11 @@ public function get_item_schema() { 'type' => 'string', 'format' => 'uri', ), + 'template' => array( + 'description' => 'A URI template for the link.', + 'type' => 'string', + 'format' => 'uri', + ), ), ), ), diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 6a2ff5453..4dde59cea 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -104,5 +104,6 @@ function http_disable_request( $response, $args, $url ) { // Start up the WP testing environment. require $_tests_dir . '/includes/bootstrap.php'; require __DIR__ . '/class-activitypub-testcase-cache-http.php'; +require __DIR__ . '/class-rest-controller-testcase.php'; \Activitypub\Migration::add_default_settings(); diff --git a/tests/class-rest-controller-testcase.php b/tests/class-rest-controller-testcase.php new file mode 100644 index 000000000..84ad99d3e --- /dev/null +++ b/tests/class-rest-controller-testcase.php @@ -0,0 +1,82 @@ +fail( + sprintf( + 'REST API URL "%s" should have a leading slash.', + $path + ) + ); + } + + return $url; + } +} diff --git a/tests/includes/rest/class-test-webfinger.php b/tests/includes/rest/class-test-webfinger-controller.php similarity index 70% rename from tests/includes/rest/class-test-webfinger.php rename to tests/includes/rest/class-test-webfinger-controller.php index a248422a8..664e90f95 100644 --- a/tests/includes/rest/class-test-webfinger.php +++ b/tests/includes/rest/class-test-webfinger-controller.php @@ -5,19 +5,16 @@ * @package Activitypub */ +namespace Activitypub\Tests\Rest; + +use Activitypub\Tests\REST_Controller_Testcase; + /** * Tests for WebFinger REST API endpoint. * - * @coversDefaultClass \Activitypub\Rest\Webfinger + * @coversDefaultClass \Activitypub\Rest\Webfinger_Controller */ -class Test_Webfinger extends \WP_UnitTestCase { - - /** - * The REST server. - * - * @var \WP_REST_Server - */ - protected $server; +class Test_Webfinger_Controller extends REST_Controller_Testcase { /** * Test user. @@ -29,9 +26,9 @@ class Test_Webfinger extends \WP_UnitTestCase { /** * Set up class test fixtures. * - * @param WP_UnitTest_Factory $factory WordPress unit test factory. + * @param \WP_UnitTest_Factory $factory WordPress unit test factory. */ - public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { + public static function wpSetUpBeforeClass( \WP_UnitTest_Factory $factory ) { self::$user = $factory->user->create_and_get( array( 'user_login' => 'test_user', @@ -43,8 +40,6 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { /** * Clean up test fixtures. - * - * @since 5.6.0 */ public static function wpTearDownAfterClass() { self::delete_user( self::$user->ID ); @@ -56,13 +51,7 @@ public static function wpTearDownAfterClass() { public function set_up() { parent::set_up(); - global $wp_rest_server; - - $wp_rest_server = new \WP_REST_Server(); - $this->server = $wp_rest_server; - \do_action( 'rest_api_init' ); - ( new \Activitypub\Rest\Webfinger() )->register_routes(); \add_filter( 'webfinger_data', array( '\Activitypub\Integration\Webfinger', 'add_pseudo_user_discovery' ), 1, 2 ); } @@ -73,7 +62,7 @@ public function set_up() { * @covers ::register_routes */ public function test_register_routes() { - $routes = $this->server->get_routes(); + $routes = rest_get_server()->get_routes(); $this->assertArrayHasKey( '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger', $routes ); } @@ -83,7 +72,11 @@ public function test_register_routes() { * @covers ::get_item_schema */ public function test_get_item_schema() { - $schema = ( new \Activitypub\Rest\Webfinger() )->get_item_schema(); + $request = new \WP_REST_Request( 'OPTIONS', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); + $response = rest_get_server()->dispatch( $request )->get_data(); + + $this->assertArrayHasKey( 'schema', $response ); + $schema = $response['schema']; $this->assertIsArray( $schema ); $this->assertArrayHasKey( 'properties', $schema ); @@ -97,11 +90,10 @@ public function test_get_item_schema() { * * @covers ::get_item */ - public function test_get_item_with_valid_resource() { + public function test_get_item() { $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); $request->set_param( 'resource', 'acct:test_user@example.org' ); - - $response = $this->server->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $this->assertStringContainsString( 'application/jrd+json', $response->get_headers()['Content-Type'] ); @@ -116,8 +108,8 @@ public function test_get_item_with_valid_resource() { public function test_get_item_with_invalid_resource() { $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); $request->set_param( 'resource', 'invalid-resource' ); + $response = rest_get_server()->dispatch( $request ); - $response = $this->server->dispatch( $request ); $this->assertEquals( 400, $response->get_status() ); } @@ -128,7 +120,8 @@ public function test_get_item_with_invalid_resource() { */ public function test_get_item_with_missing_resource() { $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); - $response = $this->server->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); + $this->assertEquals( 400, $response->get_status() ); } @@ -163,7 +156,7 @@ function( $data, $resource ) use ( $test_data ) { $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); $request->set_param( 'resource', 'acct:test_user@example.org' ); - $response = $this->server->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $this->assertEquals( $test_data, $data ); @@ -179,7 +172,7 @@ public function test_get_item_with_author_url() { $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); $request->set_param( 'resource', $author_url ); - $response = $this->server->dispatch( $request ); + $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); @@ -187,4 +180,22 @@ public function test_get_item_with_author_url() { $this->assertContains( $author_url, $data['aliases'] ); $this->assertArrayHasKey( 'links', $data ); } + + /** + * Test that the Webfinger response matches its schema. + * + * @covers ::get_item + * @covers ::get_item_schema + */ + public function test_response_matches_schema() { + $request = new \WP_REST_Request( 'GET', '/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger' ); + $request->set_param( 'resource', 'acct:test_user@example.org' ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $schema = ( new \Activitypub\Rest\Webfinger_Controller() )->get_item_schema(); + + $valid = \rest_validate_value_from_schema( $data, $schema ); + $this->assertNotWPError( $valid, 'Response failed schema validation: ' . ( \is_wp_error( $valid ) ? $valid->get_error_message() : '' ) ); + } }