Files

142 lines
5.3 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\SnapshotStore;
use LIT\MCP\Integrations\Divi\Design\SnapshotTooLarge;
use LIT\MCP\Tests\Unit\TestCase;
use RuntimeException;
final class SnapshotStoreTest extends TestCase
{
/** @var array<string, mixed> */
private array $options = [];
protected function setUp(): void
{
parent::setUp();
$this->options = [];
Functions\when('get_option')->alias(fn (string $name, mixed $default = false): mixed => $this->options[$name] ?? $default);
Functions\when('update_option')->alias(function (string $name, mixed $value, mixed $autoload = null): bool {
self::assertFalse($autoload, 'snapshots are never autoloaded');
$this->options[$name] = $value;
return true;
});
Functions\when('get_current_user_id')->justReturn(7);
}
public function testSavesListsAndReturnsTheNewestFirst(): void
{
$store = new SnapshotStore();
$first = $store->save('presets', ['module' => ['a' => 1]], 'lit/divi-presets-set');
$second = $store->save('presets', ['module' => ['a' => 2]], 'lit/divi-presets-set');
$store->attachLog('presets', $second, 42);
self::assertSame('lit_mcp_snapshots_presets', SnapshotStore::option('presets'));
self::assertMatchesRegularExpression('/^presets-\d{8}T\d{6}Z-[0-9a-f]{6}$/', $first);
$list = $store->list('presets');
self::assertSame([$second, $first], array_column($list, 'id'));
self::assertSame(42, $list[0]['log_id']);
self::assertSame(7, $list[0]['user_id']);
self::assertArrayNotHasKey('data', $list[0]);
self::assertSame(['module' => ['a' => 1]], $store->get('presets', $first)['data']);
self::assertNull($store->get('presets', 'presets-unknown'));
self::assertSame([], $store->list('colors'));
}
public function testKeepsAtMostTwentyPerArea(): void
{
$store = new SnapshotStore();
$ids = [];
for ($i = 0; $i < 22; ++$i) {
$ids[] = $store->save('presets', ['n' => $i], 'lit/divi-presets-set');
}
$list = $store->list('presets');
self::assertCount(SnapshotStore::LIMIT, $list);
self::assertSame($ids[21], $list[0]['id']);
self::assertNull($store->get('presets', $ids[0]));
self::assertNull($store->get('presets', $ids[1]));
}
/** Final review I-4: the option as a whole stays below TOTAL_BYTES; the oldest entries fall out first. */
public function testKeepsTheOptionBelowTheTotalSize(): void
{
$store = new SnapshotStore();
$big = ['x' => str_repeat('a', 1900000)];
$ids = [];
for ($i = 0; $i < 5; ++$i) {
$ids[] = $store->save('presets', $big + ['n' => $i], 'lit/divi-presets-set');
}
$list = $store->list('presets');
self::assertSame([$ids[4], $ids[3], $ids[2], $ids[1]], array_column($list, 'id'));
self::assertLessThanOrEqual(SnapshotStore::TOTAL_BYTES, array_sum(array_column($list, 'size')));
}
public function testOldEntriesOverTheTotalSizeFallOutButTheNewestStays(): void
{
$store = new SnapshotStore();
$store->save('presets', ['x' => str_repeat('a', 1900000)], 'lit/divi-presets-set');
$this->options['lit_mcp_snapshots_presets'][0]['size'] = SnapshotStore::TOTAL_BYTES + 1;
$newest = $store->save('presets', ['n' => 1], 'lit/divi-presets-set');
self::assertSame([$newest], array_column($store->list('presets'), 'id'));
}
public function testRefusesSnapshotsAboveTheLimit(): void
{
$this->expectException(SnapshotTooLarge::class);
(new SnapshotStore())->save('presets', ['x' => str_repeat('a', SnapshotStore::MAX_BYTES)], 'lit/divi-presets-set');
}
public function testUnknownAreaIsAProgrammingError(): void
{
$this->expectException(InvalidArgumentException::class);
SnapshotStore::option('pages');
}
public function testAFailedOptionWriteThrows(): void
{
Functions\when('update_option')->justReturn(false);
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('snapshot presets could not be stored');
(new SnapshotStore())->save('presets', ['a' => 1], 'lit/divi-presets-set');
}
public function testAnUnencodableSnapshotIsRefusedBeforeAnyWrite(): void
{
Functions\when('wp_json_encode')->justReturn(false);
$written = false;
Functions\when('update_option')->alias(function () use (&$written): bool {
$written = true;
return true;
});
try {
(new SnapshotStore())->save('presets', ['a' => 1], 'lit/divi-presets-set');
self::fail('an unencodable snapshot must be refused');
} catch (RuntimeException $e) {
self::assertSame('snapshot presets could not be encoded', $e->getMessage());
}
self::assertFalse($written, 'nothing may be written when the snapshot cannot be encoded');
}
public function testCorruptOptionReadsAsEmpty(): void
{
$this->options['lit_mcp_snapshots_presets'] = 'kaputt';
self::assertSame([], (new SnapshotStore())->list('presets'));
}
}