Files
WP-Content-Sync/tests/Unit/Admin/FileImportControllerTest.php
T
2026-05-01 15:23:25 -05:00

266 lines
7.7 KiB
PHP

<?php
namespace WPContentSync\Tests\Unit\Admin;
use PHPUnit\Framework\TestCase;
use WPContentSync\Admin\FileImportController;
use WPContentSync\Content\ContentHandlerInterface;
use WPContentSync\Content\ContentHandlerRegistry;
use WPContentSync\Logging\LoggerInterface;
use WPContentSync\Package\PackageChecksum;
use WPContentSync\Package\PackageValidator;
use WPContentSync\Settings\SettingsRepository;
use WPContentSync\Sync\SyncContext;
use WPContentSync\Sync\SyncEngine;
use WPContentSync\Sync\SyncResult;
use WPContentSync\Sync\SyncStateRepository;
use WPContentSync\Transport\JsonFileTransport;
class FileImportControllerTest extends TestCase {
/** @var array<int, string> */
private array $temporary_files = array();
/** @var array<int, array<string, mixed>> */
private array $logs = array();
protected function tearDown(): void {
foreach ( $this->temporary_files as $file ) {
if ( is_file( $file ) ) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink -- Removing a PHPUnit temp file.
unlink( $file );
}
}
unset(
$GLOBALS['wpcs_current_user_can'],
$GLOBALS['wpcs_nonce_valid'],
$GLOBALS['wpcs_redirect_location'],
$GLOBALS['wpcs_test_options'],
$GLOBALS['wpcs_test_option_autoloads'],
$GLOBALS['wpcs_test_transients'],
$GLOBALS['wpcs_test_transient_expiration']
);
$_FILES = array();
$this->logs = array();
parent::tearDown();
}
public function test_it_rejects_users_without_manage_options(): void {
$GLOBALS['wpcs_current_user_can']['manage_options'] = false;
$controller = $this->controller();
$this->expectException( \RuntimeException::class );
$this->expectExceptionMessage( 'You do not have permission to import content packages.' );
$controller->handleImport();
}
public function test_it_rejects_invalid_nonces(): void {
$GLOBALS['wpcs_nonce_valid']['wpcs_import_package']['wpcs_import_package_nonce'] = false;
$controller = $this->controller();
$this->expectException( \RuntimeException::class );
$this->expectExceptionMessage( 'The import request could not be verified.' );
$controller->handleImport();
}
public function test_it_rejects_missing_uploads(): void {
$controller = $this->controller();
$this->expectException( \RuntimeException::class );
$this->expectExceptionMessage( 'Choose a package JSON file before importing.' );
$controller->handleImport();
}
public function test_it_rejects_failed_uploads(): void {
$_FILES['wpcs_package_file'] = array(
'tmp_name' => '',
'error' => UPLOAD_ERR_INI_SIZE,
);
$controller = $this->controller();
$this->expectException( \RuntimeException::class );
$this->expectExceptionMessage( 'The package file could not be uploaded.' );
$controller->handleImport();
}
public function test_it_imports_valid_uploaded_packages_with_sync_engine(): void {
$file = $this->createTemporaryPackageFile( $this->validJson() );
$_FILES['wpcs_package_file'] = array(
'tmp_name' => $file,
'error' => UPLOAD_ERR_OK,
);
$this->controller()->handleImport();
self::assertStringContainsString( 'wpcs_imported=1', $GLOBALS['wpcs_redirect_location'] );
self::assertSame( 'Imported content package.', $this->logs[2]['message'] );
self::assertSame( 0, $this->logs[2]['context']['created'] );
}
public function test_it_redirects_with_error_when_sync_engine_import_fails(): void {
$file = $this->createTemporaryPackageFile( $this->validJson() );
$_FILES['wpcs_package_file'] = array(
'tmp_name' => $file,
'error' => UPLOAD_ERR_OK,
);
$this->controller( SyncResult::failure( array( 'Posts failed.' ) ) )->handleImport();
self::assertStringContainsString( 'wpcs_import_error=', $GLOBALS['wpcs_redirect_location'] );
self::assertStringContainsString( 'Posts+failed.', $GLOBALS['wpcs_redirect_location'] );
}
public function test_it_redirects_with_error_for_invalid_uploaded_packages(): void {
$file = $this->createTemporaryPackageFile( '{"schema_version":' );
$_FILES['wpcs_package_file'] = array(
'tmp_name' => $file,
'error' => UPLOAD_ERR_OK,
);
$this->controller()->handleImport();
self::assertStringContainsString( 'wpcs_import_error=', $GLOBALS['wpcs_redirect_location'] );
self::assertStringContainsString( 'not+valid+JSON', $GLOBALS['wpcs_redirect_location'] );
}
private function controller( ?SyncResult $result = null ): FileImportController {
$logger = $this->logger();
return new FileImportController(
new JsonFileTransport( new PackageValidator() ),
$logger,
$this->syncEngine( $result ?? SyncResult::success() )
);
}
private function syncEngine( SyncResult $result ): SyncEngine {
return new SyncEngine(
new ContentHandlerRegistry( array( $this->handler( $result ) ) ),
new SyncStateRepository(),
new SettingsRepository(),
$this->logger()
);
}
private function handler( SyncResult $result ): ContentHandlerInterface {
return new class( $result ) implements ContentHandlerInterface {
private SyncResult $result;
public function __construct( SyncResult $result ) {
$this->result = $result;
}
public function bucket(): string {
return 'posts';
}
public function importRecords( array $records, SyncContext $context ): SyncResult {
return $this->result;
}
};
}
private function logger(): LoggerInterface {
return new class( $this->logs ) implements LoggerInterface {
/** @var array<int, array<string, mixed>> */
private array $logs;
/**
* @param array<int, array<string, mixed>> $logs Logs.
*/
public function __construct( array &$logs ) {
$this->logs = &$logs;
}
public function error( string $message, array $context = array() ): void {
$this->record( 'error', $message, $context );
}
public function warning( string $message, array $context = array() ): void {
$this->record( 'warning', $message, $context );
}
public function info( string $message, array $context = array() ): void {
$this->record( 'info', $message, $context );
}
public function debug( string $message, array $context = array() ): void {
$this->record( 'debug', $message, $context );
}
/**
* @param array<string, mixed> $context Context.
*/
private function record( string $level, string $message, array $context ): void {
$this->logs[] = array(
'level' => $level,
'message' => $message,
'context' => $context,
);
}
};
}
private function validJson(): string {
$records = array(
'posts' => array(),
'terms' => array(),
'media' => array(),
'custom_post_types' => array(),
);
$json = wp_json_encode(
array(
'schema_version' => '1.0',
'generated_at' => '2026-04-26T20:30:00+00:00',
'source' => array(
'site_url' => 'https://example.test',
'name' => 'Example',
),
'destination' => array(
'site_url' => 'https://staging.example.test',
'name' => 'Staging',
),
'manifest' => array(
'posts' => 0,
'terms' => 0,
'media' => 0,
'custom_post_types' => 0,
),
'records' => $records,
'checksums' => array(
'records' => PackageChecksum::records( $records ),
),
)
);
if ( false === $json ) {
throw new \RuntimeException( 'Unable to create package JSON fixture.' );
}
return $json;
}
private function createTemporaryPackageFile( string $contents ): string {
$file = tempnam( sys_get_temp_dir(), 'wpcs-package-' );
if ( false === $file ) {
throw new \RuntimeException( 'Unable to create temporary package file.' );
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Creating a PHPUnit temp fixture.
file_put_contents( $file, $contents );
$this->temporary_files[] = $file;
return $file;
}
}