feat: add rest package endpoints
This commit is contained in:
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* REST package receive/status controller.
|
||||||
|
*
|
||||||
|
* @package WPContentSync
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WPContentSync\Rest;
|
||||||
|
|
||||||
|
use WPContentSync\Package\ContentPackage;
|
||||||
|
use WPContentSync\Package\PackageValidator;
|
||||||
|
|
||||||
|
final class RestPackageController {
|
||||||
|
private PackageValidator $validator;
|
||||||
|
|
||||||
|
public function __construct( PackageValidator $validator ) {
|
||||||
|
$this->validator = $validator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(): void {
|
||||||
|
add_action( 'rest_api_init', array( $this, 'registerRoutes' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerRoutes(): void {
|
||||||
|
register_rest_route(
|
||||||
|
'wp-content-sync/v1',
|
||||||
|
'/status',
|
||||||
|
array(
|
||||||
|
'methods' => 'GET',
|
||||||
|
'callback' => array( $this, 'status' ),
|
||||||
|
'permission_callback' => array( $this, 'canReceivePackage' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
register_rest_route(
|
||||||
|
'wp-content-sync/v1',
|
||||||
|
'/package',
|
||||||
|
array(
|
||||||
|
'methods' => 'POST',
|
||||||
|
'callback' => array( $this, 'receivePackage' ),
|
||||||
|
'permission_callback' => array( $this, 'canReceivePackage' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canReceivePackage(): bool {
|
||||||
|
return current_user_can( 'manage_options' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function status(): array {
|
||||||
|
return array(
|
||||||
|
'ok' => true,
|
||||||
|
'plugin' => 'wp-content-sync',
|
||||||
|
'version' => WPCS_VERSION,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $request REST request or decoded request array.
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function receivePackage( $request ): array {
|
||||||
|
$data = $this->requestData( $request );
|
||||||
|
|
||||||
|
if ( ! isset( $data['package'] ) || ! is_array( $data['package'] ) ) {
|
||||||
|
return array(
|
||||||
|
'accepted' => false,
|
||||||
|
'errors' => array( 'package is required and must be an object.' ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->validator->validate( $data['package'] );
|
||||||
|
|
||||||
|
if ( ! $result->isValid() ) {
|
||||||
|
return array(
|
||||||
|
'accepted' => false,
|
||||||
|
'errors' => $result->errors(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$package = ContentPackage::fromArray( $data['package'] );
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'accepted' => true,
|
||||||
|
'schema_version' => $package->schemaVersion(),
|
||||||
|
'manifest' => $package->manifest(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $request REST request or decoded request array.
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function requestData( $request ): array {
|
||||||
|
if ( is_array( $request ) ) {
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( is_object( $request ) && method_exists( $request, 'get_json_params' ) ) {
|
||||||
|
$params = $request->get_json_params();
|
||||||
|
|
||||||
|
return is_array( $params ) ? $params : array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Tests for REST package receive/status controller.
|
||||||
|
*
|
||||||
|
* @package WPContentSync
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace WPContentSync\Tests\Unit\Rest;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use WPContentSync\Package\PackageChecksum;
|
||||||
|
use WPContentSync\Package\PackageValidator;
|
||||||
|
use WPContentSync\Rest\RestPackageController;
|
||||||
|
|
||||||
|
class RestPackageControllerTest extends TestCase {
|
||||||
|
protected function tearDown(): void {
|
||||||
|
unset( $GLOBALS['wpcs_rest_routes'], $GLOBALS['wpcs_current_user_can'], $GLOBALS['wpcs_test_actions'] );
|
||||||
|
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_hooks_route_registration_to_rest_api_init(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
$controller->register();
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
array( $controller, 'registerRoutes' ),
|
||||||
|
$GLOBALS['wpcs_test_actions']['rest_api_init'][0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_registers_status_and_package_routes(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
$controller->registerRoutes();
|
||||||
|
|
||||||
|
self::assertArrayHasKey( 'wp-content-sync/v1/status', $GLOBALS['wpcs_rest_routes'] );
|
||||||
|
self::assertArrayHasKey( 'wp-content-sync/v1/package', $GLOBALS['wpcs_rest_routes'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_requires_manage_options_permission(): void {
|
||||||
|
$GLOBALS['wpcs_current_user_can']['manage_options'] = false;
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
|
||||||
|
self::assertFalse( $controller->canReceivePackage() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_returns_status_payload(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
array(
|
||||||
|
'ok' => true,
|
||||||
|
'plugin' => 'wp-content-sync',
|
||||||
|
'version' => WPCS_VERSION,
|
||||||
|
),
|
||||||
|
$controller->status()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_accepts_valid_packages(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
$this->acceptedResponse(),
|
||||||
|
$controller->receivePackage(
|
||||||
|
array(
|
||||||
|
'package' => $this->validPackage(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_accepts_rest_request_like_objects(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
$request = new class(
|
||||||
|
array(
|
||||||
|
'package' => $this->validPackage(),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
/** @var array<string, mixed> */
|
||||||
|
private array $params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $params Request params.
|
||||||
|
*/
|
||||||
|
public function __construct( array $params ) {
|
||||||
|
$this->params = $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function get_json_params(): array {
|
||||||
|
return $this->params;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self::assertSame( $this->acceptedResponse(), $controller->receivePackage( $request ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_it_rejects_invalid_package_shapes(): void {
|
||||||
|
$controller = new RestPackageController( new PackageValidator() );
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
array(
|
||||||
|
'accepted' => false,
|
||||||
|
'errors' => array( 'package is required and must be an object.' ),
|
||||||
|
),
|
||||||
|
$controller->receivePackage( array() )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function acceptedResponse(): array {
|
||||||
|
return array(
|
||||||
|
'accepted' => true,
|
||||||
|
'schema_version' => '1.0',
|
||||||
|
'manifest' => array(
|
||||||
|
'posts' => 0,
|
||||||
|
'terms' => 0,
|
||||||
|
'media' => 0,
|
||||||
|
'custom_post_types' => 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function validPackage(): array {
|
||||||
|
$records = array(
|
||||||
|
'posts' => array(),
|
||||||
|
'terms' => array(),
|
||||||
|
'media' => array(),
|
||||||
|
'custom_post_types' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'schema_version' => '1.0',
|
||||||
|
'generated_at' => '2026-04-28T12:00:00+00:00',
|
||||||
|
'source' => array(
|
||||||
|
'site_url' => 'https://example.test',
|
||||||
|
'name' => 'Example',
|
||||||
|
),
|
||||||
|
'destination' => array(
|
||||||
|
'site_url' => 'https://destination.test',
|
||||||
|
'name' => 'Destination',
|
||||||
|
),
|
||||||
|
'manifest' => array(
|
||||||
|
'posts' => 0,
|
||||||
|
'terms' => 0,
|
||||||
|
'media' => 0,
|
||||||
|
'custom_post_types' => 0,
|
||||||
|
),
|
||||||
|
'records' => $records,
|
||||||
|
'checksums' => array(
|
||||||
|
'records' => PackageChecksum::records( $records ),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -466,6 +466,34 @@ if ( ! function_exists( 'is_wp_error' ) ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( ! function_exists( 'register_rest_route' ) ) {
|
||||||
|
/**
|
||||||
|
* Minimal REST route registrar for unit tests.
|
||||||
|
*
|
||||||
|
* @param string $rest_namespace REST namespace.
|
||||||
|
* @param string $route REST route.
|
||||||
|
* @param array<string, mixed> $args Route arguments.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function register_rest_route( $rest_namespace, $route, array $args ) {
|
||||||
|
$GLOBALS['wpcs_rest_routes'][ $rest_namespace . $route ] = $args;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! function_exists( 'rest_ensure_response' ) ) {
|
||||||
|
/**
|
||||||
|
* Minimal REST response wrapper for unit tests.
|
||||||
|
*
|
||||||
|
* @param mixed $response Response value.
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function rest_ensure_response( $response ) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! function_exists( 'admin_url' ) ) {
|
if ( ! function_exists( 'admin_url' ) ) {
|
||||||
/**
|
/**
|
||||||
* Minimal admin URL helper for unit tests.
|
* Minimal admin URL helper for unit tests.
|
||||||
|
|||||||
Reference in New Issue
Block a user