Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Pages/DiviSaverTest.php
T

304 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
use Brain\Monkey\Functions;
use LIT\MCP\Abilities\WriteFailed;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
use WP_Error;
use WP_Post;
final class DiviSaverTest extends TestCase
{
private WP_Post $post;
/** @var list<string> */
private array $calls = [];
/** @var array<string, string> */
private array $meta = [];
private string $log = '';
private string|false $previousLog = false;
protected function setUp(): void
{
parent::setUp();
$this->post = new WP_Post(['ID' => 7, 'post_type' => 'page', 'post_content' => 'ALT']);
Functions\when('wp_save_post_revision')->alias(function (): int {
$this->calls[] = 'revision';
return 811;
});
Functions\when('wp_get_post_revisions')->justReturn([811 => new WP_Post(['ID' => 811])]);
Functions\when('update_post_meta')->alias(function (int $id, string $key, string $value): bool {
$this->calls[] = "meta:$key=$value";
$this->meta[$key] = $value;
return true;
});
Functions\when('get_post_meta')->alias(fn (int $id, string $key): string => $this->meta[$key] ?? '');
Functions\when('kses_remove_filters')->alias(function (): void {
$this->calls[] = 'kses off';
});
Functions\when('kses_init_filters')->alias(function (): void {
$this->calls[] = 'kses on';
});
// FollowUp logs failures with error_log() (a PHP internal): keep the test output clean.
$this->log = (string) tempnam(sys_get_temp_dir(), 'lit-log');
$this->previousLog = ini_set('error_log', $this->log);
}
protected function tearDown(): void
{
ini_set('error_log', (string) $this->previousLog);
@unlink($this->log);
parent::tearDown();
}
public function testSavesWithoutKsesForFilteredUsersAndSetsDiviFlags(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('has_filter')->justReturn(10);
Functions\expect('wp_update_post')->once()->with(['ID' => 7, 'post_content' => 'NEU'], true)
->andReturnUsing(function (): int {
$this->calls[] = 'update';
return 7;
});
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_content' => 'NEU']));
$saved = (new DiviSaver())->save($this->post, 'NEU');
self::assertSame(['revision_id' => 811, 'warnings' => []], $saved);
self::assertSame(
// Ruling S37: the restore-point revision is captured inside the same kses-off window –
// it copies the stored content, and kses would strip an admin's embeds from it.
[
'kses off', 'revision', 'update', 'kses on',
'meta:_et_pb_use_divi_5=on', 'meta:_et_pb_use_builder=on', 'meta:_et_pb_show_page_creation=off',
],
$this->calls
);
}
public function testKeepsKsesUntouchedForUnfilteredUsersAndWarnsWhenAFilterChangedTheContent(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_update_post')->justReturn(7);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_content' => 'NEU, gefiltert']));
$saved = (new DiviSaver())->save($this->post, 'NEU');
self::assertNotContains('kses off', $this->calls);
self::assertSame(['revision'], array_slice($this->calls, 0, 1));
self::assertSame([DiviSaver::CHANGED_ON_SAVE], $saved['warnings']);
}
public function testKsesStaysUntouchedWhenItIsNotActive(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('has_filter')->justReturn(false);
Functions\when('wp_update_post')->justReturn(7);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_content' => 'NEU']));
(new DiviSaver())->save($this->post, 'NEU');
self::assertNotContains('kses off', $this->calls);
self::assertNotContains('kses on', $this->calls);
}
public function testRefusedUpdateIsACleanFailure(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_update_post')->justReturn(new WP_Error('db_update_error', 'DB kaputt'));
try {
(new DiviSaver())->save($this->post, 'NEU');
self::fail('WriteFailed expected');
} catch (WriteFailed $e) {
self::assertTrue($e->rolledBack);
self::assertSame(811, $e->revisionId);
self::assertStringContainsString('Revision 811 bleibt erhalten', $e->toError()->get_error_message());
}
}
public function testAThrowingHookRestoresThePreviousContentAndKsesComesBack(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('has_filter')->justReturn(10);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_status' => 'draft']));
$written = [];
Functions\when('wp_update_post')->alias(function (array $data) use (&$written): int {
$written[] = $data['post_content'];
$this->calls[] = 'update';
if ($data['post_content'] === 'NEU') {
throw new RuntimeException('save_post hook failed');
}
return 7;
});
try {
(new DiviSaver())->save($this->post, 'NEU');
self::fail('WriteFailed expected');
} catch (WriteFailed $e) {
self::assertTrue($e->rolledBack);
self::assertSame(['NEU', 'ALT'], $written);
// The kses bypass is restored in finally – after the failed save and after the restore.
self::assertSame(
['kses off', 'revision', 'update', 'kses on', 'kses off', 'update', 'kses on'],
$this->calls
);
}
}
public function testFailedRestoreIsAPartialWrite(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_status' => 'draft']));
Functions\when('wp_update_post')->alias(static function (): int {
throw new RuntimeException('database gone');
});
try {
(new DiviSaver())->save($this->post, 'NEU');
self::fail('WriteFailed expected');
} catch (WriteFailed $e) {
self::assertFalse($e->rolledBack);
self::assertToolError($e->toError(), 'partial_write');
}
}
public function testRestoreNeverPublishesAMissedSchedule(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('get_post')->justReturn(new WP_Post([
'ID' => 7,
'post_status' => 'future',
'post_date_gmt' => '2020-01-01 00:00:00',
]));
$written = [];
Functions\when('wp_update_post')->alias(static function (array $data) use (&$written): int {
$written[] = $data['post_content'];
throw new RuntimeException('save_post hook failed');
});
try {
(new DiviSaver())->save($this->post, 'NEU');
self::fail('WriteFailed expected');
} catch (WriteFailed $e) {
self::assertFalse($e->rolledBack);
self::assertSame(['NEU'], $written);
}
}
public function testFlagFailureIsOnlyAWarning(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_update_post')->justReturn(7);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_content' => 'NEU']));
Functions\when('get_post_meta')->justReturn('');
$saved = (new DiviSaver())->save($this->post, 'NEU');
self::assertSame(811, $saved['revision_id']);
self::assertCount(1, $saved['warnings']);
self::assertStringContainsString('Divi-Kennzeichen', $saved['warnings'][0]);
self::assertStringContainsString('divi builder flags failed', (string) file_get_contents($this->log));
}
public function testSaveNewWritesTheFirstContentWithoutKsesAndSetsTheFlagsStrictly(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('has_filter')->justReturn(10);
Functions\expect('wp_update_post')->once()->with(['ID' => 42, 'post_content' => 'NEU'], true)
->andReturnUsing(function (): int {
$this->calls[] = 'update';
return 42;
});
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 42, 'post_content' => 'NEU']));
Functions\expect('wp_save_post_revision')->never();
self::assertSame([], (new DiviSaver())->saveNew(42, 'NEU'));
self::assertSame(
// Same kses window as save(), but no restore point: the new draft has no earlier state.
[
'kses off', 'update', 'kses on',
'meta:_et_pb_use_divi_5=on', 'meta:_et_pb_use_builder=on', 'meta:_et_pb_show_page_creation=off',
],
$this->calls
);
}
public function testSaveNewReportsAContentChangedOnSaveAndKeepsKsesForUnfilteredUsers(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('has_filter')->justReturn(10);
Functions\when('wp_update_post')->justReturn(42);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 42, 'post_content' => 'ANDERS']));
self::assertSame([DiviSaver::CHANGED_ON_SAVE], (new DiviSaver())->saveNew(42, 'NEU'));
self::assertNotContains('kses off', $this->calls);
}
public function testSaveNewThrowsWhenTheUpdateIsRefusedOrAFlagIsMissing(): void
{
Functions\when('current_user_can')->justReturn(false);
Functions\when('has_filter')->justReturn(10);
Functions\when('wp_update_post')->justReturn(new WP_Error('db', 'kaputt'));
try {
(new DiviSaver())->saveNew(42, 'NEU');
self::fail('RuntimeException expected');
} catch (RuntimeException $e) {
self::assertStringContainsString('kaputt', $e->getMessage());
}
self::assertSame(['kses off', 'kses on'], $this->calls, 'kses comes back, no flags on a failed write');
Functions\when('wp_update_post')->justReturn(42);
Functions\when('get_post_meta')->justReturn('');
$this->expectException(RuntimeException::class);
(new DiviSaver())->saveNew(42, 'NEU');
}
public function testLibraryItemsGetNoDiviFlagsAndGlobalOnesClearEveryPage(): void
{
\ET_Core_PageResource::$removed = [];
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_update_post')->justReturn(7);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 7, 'post_content' => 'NEU']));
$cleared = [];
Functions\when('et_theme_builder_clear_wp_cache')->alias(static function (string $what) use (&$cleared): void {
$cleared[] = $what;
});
(new DiviSaver())->save($this->post, 'NEU', SaveScope::LibraryItem);
self::assertSame([], array_filter($this->calls, static fn (string $call): bool => str_starts_with($call, 'meta:')));
self::assertSame([[7, 'all', true]], \ET_Core_PageResource::$removed);
self::assertSame([], $cleared);
\ET_Core_PageResource::$removed = [];
(new DiviSaver())->save($this->post, 'NEU', SaveScope::GlobalItem);
self::assertSame([[7, 'all', true], ['all', 'all', true]], \ET_Core_PageResource::$removed);
self::assertSame(['all'], $cleared);
}
public function testSaveNewOfALibraryItemSetsNoFlags(): void
{
Functions\when('current_user_can')->justReturn(true);
Functions\when('wp_update_post')->justReturn(42);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 42, 'post_content' => 'NEU']));
self::assertSame([], (new DiviSaver())->saveNew(42, 'NEU', SaveScope::LibraryItem));
self::assertSame([], array_filter($this->calls, static fn (string $call): bool => str_starts_with($call, 'meta:')));
}
}