Files
Jan-Niclas LoosenandClaude Opus 5.5 b511ef6348 refactor(compat): Prüfbausteine nach src/Compatibility, Matrix und ManifestCheck generisch
Requirement, Call, ConstantRequirement, Mirror, Problem, FeatureStatus, CompatibilityMatrix, Manifest, ManifestCheck sowie Fingerprints und LineDiff liegen jetzt im divi-freien Kern. Neu: GlobalRequirement, CompatibilityMatrix::fromProblems(), ManifestCheck-Option signatures, DiviManifestCheck::create() und DiviAreas. Divi-Verhalten unverändert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 12:03:30 +01:00

164 lines
7.3 KiB
PHP
Raw Permalink 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\Release;
use Brain\Monkey\Functions;
use LIT\MCP\Compatibility\ReleaseState;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
use LIT\MCP\Integrations\Divi\Library\LibraryItemSpec;
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
use LIT\MCP\Integrations\WordPress\Content\NewDraft;
use LIT\MCP\Tests\Support\FakeChangeLog;
use LIT\MCP\Tests\Support\ReleaseMatrices;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
use Throwable;
use WP_Post;
/**
* Spec §5.5, D5: every writing adapter method refuses before its first write exactly when a feature it needs is
* locked – and passes the gate when only other features are locked. (DesignChange: DesignChangeTest.)
*/
final class ReleaseGuardsTest extends TestCase
{
private const WRITES = ['wp_update_post', 'wp_insert_post', 'update_post_meta', 'add_post_meta', 'delete_post_meta',
'update_option', 'delete_option', 'wp_set_object_terms'];
protected function setUp(): void
{
parent::setUp();
// Nothing may be written while locked: every write function WordPress offers fails the test.
foreach (self::WRITES as $write) {
Functions\when($write)->alias(static function () use ($write): never {
self::fail($write . ' ran although Divi is locked');
});
}
Functions\when('current_user_can')->justReturn(true);
}
/** @return iterable<string, array{callable(): mixed, list<DiviFeature>}> */
public static function writes(): iterable
{
$post = new WP_Post(['ID' => 7, 'post_content' => '', 'post_type' => 'page']);
$spec = new LibraryItemSpec('Hero', 'section', false, 'regular', null, 'page');
$page = [DiviFeature::PageWrite];
$tb = [DiviFeature::ThemeBuilderWrite];
yield 'save' => [static fn () => (new DiviSaver())->save($post, '<!-- wp:divi/section /-->'), $page];
yield 'save library item' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::LibraryItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'save global item' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::GlobalItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'save tb layout' => [
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::ThemeBuilderLayout),
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
];
yield 'saveNew' => [static fn () => (new DiviSaver())->saveNew(7, '<!-- wp:divi/section /-->'), $page];
yield 'saveNew library item' => [
static fn () => (new DiviSaver())->saveNew(7, 'x', SaveScope::LibraryItem),
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
];
yield 'pages setFlags' => [static fn () => DiviPages::setFlags(7), $page];
yield 'library create' => [static fn () => (new DiviLibraryStore())->create(
$spec,
new NewDraft(null, FakeChangeLog::forbidden(), 'lit/library-save', 'et_pb_layout', 'Bibliothekseintrag')
), [DiviFeature::LibraryWrite]];
yield 'tb liveId create' => [static fn () => (new DiviThemeBuilder())->liveId(true), $tb];
yield 'tb insertLayout' => [static fn () => (new DiviThemeBuilder())->insertLayout('header', 'Kopf'), $tb];
yield 'tb storeTemplate' => [static fn () => (new DiviThemeBuilder())->storeTemplate(3, [
'title' => 'V', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
]), $tb];
yield 'tb attach' => [static fn () => (new DiviThemeBuilder())->attach(3, 9), $tb];
yield 'tb detach' => [static fn () => (new DiviThemeBuilder())->detach(3, 9), $tb];
yield 'tb setConditions' => [static fn () => (new DiviThemeBuilder())->setConditions(9, ['singular'], []), $tb];
yield 'tb setEnabled' => [static fn () => (new DiviThemeBuilder())->setEnabled(9, true), $tb];
yield 'tb unmarkUnused' => [static fn () => (new DiviThemeBuilder())->unmarkUnused(9), $tb];
yield 'design savePresets' => [
static fn () => (new DesignSystemWriter())->savePresets(['module' => [], 'group' => []], false),
[DiviFeature::PresetWrite],
];
}
/**
* @dataProvider writes
* @param list<DiviFeature> $needs
*/
public function testRefusesBeforeTheFirstWriteWhenAFeatureItNeedsIsLocked(callable $write, array $needs): void
{
foreach ([ReleaseState::Untested, ReleaseState::Incompatible] as $state) {
foreach ($needs as $feature) {
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([$feature], $state));
try {
$write();
self::fail(sprintf('%s %s must refuse', $feature->value, $state->value));
} catch (DiviLocked $locked) {
self::assertSame([$feature], $locked->features);
}
}
}
}
/**
* @dataProvider writes
* @param list<DiviFeature> $needs
*/
public function testPassesTheGateWhenOnlyOtherFeaturesAreLocked(callable $write, array $needs): void
{
$others = array_values(array_filter(
DiviFeature::cases(),
static fn (DiviFeature $feature): bool => ! in_array($feature, $needs, true)
));
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::status($others, ReleaseState::Incompatible)
);
// Past the gate the method may write or fail on the missing Divi – anything but the lock.
foreach (self::WRITES as $name) {
Functions\when($name)->alias(static function () use ($name): never {
throw new RuntimeException($name . ' reached');
});
}
try {
$write();
} catch (DiviLocked $locked) {
self::fail('refused although only other features are locked: ' . $locked->getMessage());
} catch (Throwable) {
// Reached the code behind the gate.
}
self::addToAssertionCount(1);
}
public function testTheCheckPageMayBeWrittenWhileProbing(): void
{
ReleaseGate::useResolver(
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.3.0')
);
Functions\when('wp_update_post')->justReturn(41);
Functions\when('update_post_meta')->justReturn(true);
Functions\when('get_post_meta')->alias(
static fn (int $id, string $key): string => $key === '_et_pb_show_page_creation' ? 'off' : 'on'
);
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 41, 'post_content' => 'x']));
Functions\when('has_filter')->justReturn(false);
$warnings = ReleaseGate::whileProbing(41, static fn () => (new DiviSaver())->saveNew(41, 'x'));
self::assertSame([], $warnings);
}
}