Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
98 lines
3.5 KiB
PHP
98 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use InvalidArgumentException;
|
|
use LIT\MCP\Integrations\Divi\Design\DesignLock;
|
|
use LIT\MCP\Tests\Support\LockTable;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
|
|
/** Audits L5/L6: one shared lock for colors and theme options; atomic acquire and token-checked release. */
|
|
final class DesignLockTest extends TestCase
|
|
{
|
|
private function table(): LockTable
|
|
{
|
|
return LockTable::install();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
unset($GLOBALS['wpdb']);
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function testAcquireIsExclusiveUntilReleased(): void
|
|
{
|
|
$db = $this->table();
|
|
$lock = new DesignLock();
|
|
|
|
self::assertTrue($lock->acquire('presets'));
|
|
self::assertMatchesRegularExpression('/^\d+:[0-9a-f]{8}$/', $db->rows['lit_mcp_lock_presets']);
|
|
self::assertFalse((new DesignLock())->acquire('presets'));
|
|
self::assertTrue($lock->acquire('variables'), 'areas lock independently');
|
|
|
|
$lock->release('presets');
|
|
self::assertSame([], array_diff_key($db->rows, ['lit_mcp_lock_variables' => 1]));
|
|
self::assertTrue((new DesignLock())->acquire('presets'));
|
|
}
|
|
|
|
/** Audit L5: both areas rewrite the whole et_divi array, so they exclude each other. */
|
|
public function testColorsAndThemeOptionsShareOneLock(): void
|
|
{
|
|
$this->table();
|
|
|
|
self::assertSame(DesignLock::option('colors'), DesignLock::option('theme_options'));
|
|
self::assertTrue((new DesignLock())->acquire('colors'));
|
|
self::assertFalse((new DesignLock())->acquire('theme_options'));
|
|
self::assertTrue((new DesignLock())->acquire('variables'));
|
|
}
|
|
|
|
public function testAStaleLockIsTakenOverExactlyOnce(): void
|
|
{
|
|
$db = $this->table();
|
|
$db->rows['lit_mcp_lock_presets'] = (time() - DesignLock::STALE_AFTER - 1) . ':old';
|
|
|
|
self::assertTrue((new DesignLock())->acquire('presets'));
|
|
self::assertGreaterThan(time() - 5, (int) $db->rows['lit_mcp_lock_presets']);
|
|
self::assertFalse((new DesignLock())->acquire('presets'), 'the second taker sees a fresh lock');
|
|
}
|
|
|
|
/** Audit L6: a holder whose lock went stale and was taken over must not free the new holder's lock. */
|
|
public function testReleaseOnlyDeletesTheOwnToken(): void
|
|
{
|
|
$db = $this->table();
|
|
$old = new DesignLock();
|
|
self::assertTrue($old->acquire('presets'));
|
|
$db->rows['lit_mcp_lock_presets'] = (time() - DesignLock::STALE_AFTER - 1) . ':x';
|
|
$new = new DesignLock();
|
|
self::assertTrue($new->acquire('presets'));
|
|
|
|
$old->release('presets');
|
|
|
|
self::assertArrayHasKey('lit_mcp_lock_presets', $db->rows);
|
|
self::assertFalse((new DesignLock())->acquire('presets'));
|
|
$new->release('presets');
|
|
self::assertArrayNotHasKey('lit_mcp_lock_presets', $db->rows);
|
|
}
|
|
|
|
public function testAFreshLockIsNotTakenOver(): void
|
|
{
|
|
$db = $this->table();
|
|
$db->rows['lit_mcp_lock_presets'] = (time() - 10) . ':x';
|
|
|
|
self::assertFalse((new DesignLock())->acquire('presets'));
|
|
}
|
|
|
|
public function testOptionNamesAndUnknownAreas(): void
|
|
{
|
|
self::assertSame('lit_mcp_lock_divi_options', DesignLock::option('theme_options'));
|
|
self::assertSame('lit_mcp_lock_presets', DesignLock::option('presets'));
|
|
|
|
$this->expectException(InvalidArgumentException::class);
|
|
DesignLock::option('pages');
|
|
}
|
|
}
|