feat: lit/tb-create-template – disabled templates, all or nothing
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\ThemeBuilder;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Abilities\Errors;
|
||||
use LIT\MCP\Abilities\WriteFailed;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\TemplateBuild;
|
||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||
use LIT\MCP\Tests\Support\ThemeBuilderFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
use WP_Post;
|
||||
|
||||
final class TemplateBuildTest extends TestCase
|
||||
{
|
||||
public function testRollsBackEveryCreatedPostInReverse(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store();
|
||||
$store->shouldReceive('insertLayout')->andReturn(2101, 2102);
|
||||
$store->shouldReceive('storeTemplate')->andReturn(2100);
|
||||
$store->shouldReceive('attach')->once()->with(1064, 2100);
|
||||
$store->shouldReceive('detach')->once()->with(1064, 2100);
|
||||
$deleted = [];
|
||||
Functions\when('wp_delete_post')->alias(static function (int $id, bool $force) use (&$deleted): WP_Post {
|
||||
$deleted[] = [$id, $force];
|
||||
|
||||
return new WP_Post(['ID' => $id]);
|
||||
});
|
||||
$build = new TemplateBuild($store, FakeChangeLog::forbidden(), 'lit/tb-create-template');
|
||||
$build->layout('header', 'Kopf');
|
||||
$build->layout('body', 'Inhalt');
|
||||
$build->template(1064, ['title' => 'Person', 'layouts' => [], 'use_on' => [], 'exclude_from' => []]);
|
||||
$build->attach(1064, 2100);
|
||||
|
||||
try {
|
||||
$build->rollBack(new RuntimeException('Divi dropped a layout'));
|
||||
self::fail('no WriteFailed');
|
||||
} catch (WriteFailed $e) {
|
||||
self::assertTrue($e->rolledBack);
|
||||
}
|
||||
self::assertSame([[2100, true], [2102, true], [2101, true]], $deleted);
|
||||
}
|
||||
|
||||
/** Ruling Q4/B4: the partial write is logged as a partial row naming the posts left behind. */
|
||||
public function testAFailedDeleteIsAPartialWrite(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store();
|
||||
$store->shouldReceive('insertLayout')->andReturn(2101);
|
||||
Functions\when('wp_delete_post')->justReturn(false);
|
||||
$log = FakeChangeLog::partialsOnly();
|
||||
$build = new TemplateBuild($store, $log, 'lit/tb-create-template');
|
||||
$build->layout('body', 'Inhalt');
|
||||
|
||||
try {
|
||||
$build->rollBack(new RuntimeException('store failed'));
|
||||
self::fail('no WriteFailed');
|
||||
} catch (WriteFailed $e) {
|
||||
self::assertFalse($e->rolledBack);
|
||||
self::assertSame([2101], $e->details['created_ids']);
|
||||
self::assertToolError($e->toError(), 'partial_write');
|
||||
}
|
||||
$log->assertPartials([
|
||||
'ability' => 'lit/tb-create-template',
|
||||
'object_type' => 'et_template',
|
||||
'object_id' => '2101',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testARefusalAfterTheInsertRemovesTheNewPostsAndReturnsTheRefusal(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store();
|
||||
$store->shouldReceive('insertLayout')->andReturn(2101);
|
||||
Functions\expect('wp_delete_post')->once()->with(2101, true)->andReturn(new WP_Post(['ID' => 2101]));
|
||||
$build = new TemplateBuild($store, FakeChangeLog::forbidden(), 'lit/tb-create-template');
|
||||
$build->layout('body', 'Inhalt');
|
||||
$refusal = Errors::invalidInput('Divi-Inhalt ist ungültig; nichts wurde angelegt.', ['body.nodes' => 'x']);
|
||||
|
||||
self::assertSame($refusal, $build->refuse($refusal));
|
||||
}
|
||||
|
||||
public function testAFailedCleanupAfterARefusalIsAPartialWrite(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store();
|
||||
$store->shouldReceive('insertLayout')->andReturn(2101);
|
||||
Functions\when('wp_delete_post')->justReturn(null);
|
||||
$log = FakeChangeLog::partialsOnly();
|
||||
$build = new TemplateBuild($store, $log, 'lit/tb-create-template');
|
||||
$build->layout('body', 'Inhalt');
|
||||
|
||||
try {
|
||||
$build->refuse(Errors::invalidInput('x', ['body.nodes' => 'x']));
|
||||
self::fail('no WriteFailed');
|
||||
} catch (WriteFailed $e) {
|
||||
self::assertFalse($e->rolledBack);
|
||||
}
|
||||
$log->assertPartials(['object_id' => '2101']);
|
||||
}
|
||||
|
||||
/** Ruling Q8/B12: a live Theme Builder post Divi creates for this build is part of it (all or nothing). */
|
||||
public function testALiveThemeBuilderPostCreatedHereIsRolledBackToo(): void
|
||||
{
|
||||
$store = Mockery::mock(DiviThemeBuilder::class);
|
||||
$store->shouldReceive('liveId')->once()->with(false)->andReturn(0);
|
||||
$store->shouldReceive('liveId')->once()->with(true)->andReturn(3000);
|
||||
$store->shouldReceive('insertLayout')->andReturn(2101);
|
||||
$deleted = [];
|
||||
Functions\when('wp_delete_post')->alias(static function (int $id) use (&$deleted): WP_Post {
|
||||
$deleted[] = $id;
|
||||
|
||||
return new WP_Post(['ID' => $id]);
|
||||
});
|
||||
$build = new TemplateBuild($store, FakeChangeLog::forbidden(), 'lit/tb-create-template');
|
||||
$build->layout('body', 'Inhalt');
|
||||
|
||||
self::assertSame(3000, $build->themeBuilder());
|
||||
self::assertSame([2101, 3000], $build->created());
|
||||
try {
|
||||
$build->rollBack(new RuntimeException('later failure'));
|
||||
} catch (WriteFailed) {
|
||||
}
|
||||
self::assertSame([3000, 2101], $deleted);
|
||||
}
|
||||
|
||||
public function testAnExistingLiveThemeBuilderPostIsNeverDeleted(): void
|
||||
{
|
||||
$store = Mockery::mock(DiviThemeBuilder::class);
|
||||
$store->shouldReceive('liveId')->once()->with(false)->andReturn(1064);
|
||||
$store->shouldReceive('liveId')->with(true)->never();
|
||||
Functions\expect('wp_delete_post')->never();
|
||||
$build = new TemplateBuild($store, FakeChangeLog::forbidden(), 'lit/tb-create-template');
|
||||
|
||||
self::assertSame(1064, $build->themeBuilder());
|
||||
self::assertSame([], $build->created());
|
||||
$this->expectException(WriteFailed::class);
|
||||
$build->rollBack(new RuntimeException('later failure'));
|
||||
}
|
||||
|
||||
public function testNoLiveThemeBuilderPostThrows(): void
|
||||
{
|
||||
$store = Mockery::mock(DiviThemeBuilder::class);
|
||||
$store->shouldReceive('liveId')->andReturn(0);
|
||||
$build = new TemplateBuild($store, FakeChangeLog::forbidden(), 'lit/tb-create-template');
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$build->themeBuilder();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user