Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Release/DiviCheckRouteTest.php
T
Jan-Niclas LoosenandClaude Opus 5.5 7d6a97a54e refactor(compat): Freigabe-Bausteine generisch
ReleaseStore, CheckReportStore, CheckReport, CheckStep und CompatibilityCheck liegen im Kern und arbeiten je Profil (Optionen lit_mcp_<id>_releases|_check_report; Divi behält Namen und Schlüssel divi_version). Divi hängt Katalog-, Format- und Schreibtest über Profile::extraSteps an. Ein roter Prüfbericht für den aktuellen Stand sperrt LockOnFailure-Profile bis zur nächsten grünen Prüfung; Sperrgründe nennen nur Schritt-Labels, Ausnahmen gehen ins Error-Log. Profile bekommt testedVersions, stateKey, extraSteps, manifestCheck und reportVersionKey und wird mit benannten Argumenten gebaut.

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

162 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
use Brain\Monkey\Functions;
use LIT\MCP\Compatibility\CheckReport;
use LIT\MCP\Compatibility\CheckReportStore;
use LIT\MCP\Compatibility\CompatibilityCheck;
use LIT\MCP\Integrations\Divi\Release\DiviCheckRoute;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
use RuntimeException;
final class DiviCheckRouteTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Functions\when('admin_url')->alias(static fn (string $path = ''): string => 'https://k.de/wp-admin/' . $path);
Functions\when('add_query_arg')->alias(
static fn (array $args, string $url): string => $url . '?' . http_build_query($args)
);
Functions\when('get_current_user_id')->justReturn(1);
}
protected function tearDown(): void
{
unset($GLOBALS['wpdb']);
parent::tearDown();
}
/** Mock of the global $wpdb, its INSERT IGNORE returning $inserted rows affected. */
private static function wpdb(int $inserted): \wpdb
{
$db = Mockery::mock(\wpdb::class)->makePartial();
$db->expects('query')->once()->with(Mockery::on(static function (string $sql): bool {
return str_starts_with(
$sql,
"INSERT IGNORE INTO wp_options (option_name, option_value, autoload) "
. "VALUES ('lit_mcp_divi_check_lock', '"
) && str_ends_with($sql, "', 'no')");
}))->andReturn($inserted);
$GLOBALS['wpdb'] = $db;
return $db;
}
public function testRegistersAPostRouteForAdministratorsOnly(): void
{
Functions\expect('register_rest_route')->once()->with(
'lit-mcp/v1',
'/divi-check',
Mockery::on(static function (array $args): bool {
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'manage_options');
$allowed = ($args['permission_callback'])();
Functions\when('current_user_can')->justReturn(false);
return $args['methods'] === 'POST' && $allowed === true && ($args['permission_callback'])() === false;
})
);
(new DiviCheckRoute(static fn () => null))->registerRoute();
}
public function testRunsTheCheckStoresTheReportAndRedirectsToTheTab(): void
{
self::wpdb(1);
$report = new CheckReport('r1', 'd', 1, '5.3.0', '0.6.0', 'k', []);
$check = Mockery::mock(CompatibilityCheck::class);
$check->expects('run')->with(1)->andReturn($report);
$reports = Mockery::mock(CheckReportStore::class);
$reports->expects('save')->with($report);
Functions\expect('delete_option')->once()->with(DiviCheckRoute::LOCK);
$response = (new DiviCheckRoute(static fn () => $check, $reports))->handle();
self::assertSame(303, $response->get_status());
self::assertStringContainsString('tab=divi', $response->get_headers()['Location']);
self::assertStringContainsString('lit_mcp_notice=divi_checked', $response->get_headers()['Location']);
}
public function testASecondCheckWaitsForTheFirst(): void
{
$db = self::wpdb(0);
$db->expects('get_var')->once()->andReturn((string) time());
Functions\expect('delete_option')->never();
$reports = Mockery::mock(CheckReportStore::class);
$reports->expects('save')->never();
$response = (new DiviCheckRoute(static fn () => self::fail('no second check'), $reports))->handle();
self::assertStringContainsString('lit_mcp_notice=divi_check_running', $response->get_headers()['Location']);
}
public function testAStaleLockIsTakenOver(): void
{
$db = self::wpdb(0);
$db->expects('get_var')->once()->andReturn('1');
$db->expects('query')->once()->with(Mockery::on(static function (string $sql): bool {
return str_starts_with($sql, "UPDATE wp_options SET option_value = '")
&& str_ends_with(
$sql,
"' WHERE option_name = 'lit_mcp_divi_check_lock' AND option_value = '1'"
);
}))->andReturn(1);
Functions\expect('delete_option')->once()->with(DiviCheckRoute::LOCK);
$check = Mockery::mock(CompatibilityCheck::class);
$check->expects('run')->andReturn(new CheckReport('r1', 'd', 1, '5.3.0', '0.6.0', 'k', []));
$reports = Mockery::mock(CheckReportStore::class);
$reports->expects('save');
$response = (new DiviCheckRoute(static fn () => $check, $reports))->handle();
self::assertStringContainsString('lit_mcp_notice=divi_checked', $response->get_headers()['Location']);
}
public function testATakeoverLostToAnotherRequestWaitsForIt(): void
{
$db = self::wpdb(0);
$db->expects('get_var')->once()->andReturn('1');
$db->expects('query')->once()->with(Mockery::on(static fn (string $sql): bool => str_starts_with(
$sql,
"UPDATE wp_options SET option_value = '"
)))->andReturn(0);
Functions\expect('delete_option')->never();
$reports = Mockery::mock(CheckReportStore::class);
$reports->expects('save')->never();
$response = (new DiviCheckRoute(static fn () => self::fail('no second check'), $reports))->handle();
self::assertStringContainsString('lit_mcp_notice=divi_check_running', $response->get_headers()['Location']);
}
public function testTheLockIsReleasedAndTheCheckMarkedFailedWhenItThrows(): void
{
self::wpdb(1);
Functions\expect('delete_option')->once()->with(DiviCheckRoute::LOCK);
$check = Mockery::mock(CompatibilityCheck::class);
$check->expects('run')->andThrow(new RuntimeException('divi kaputt'));
$reports = Mockery::mock(CheckReportStore::class);
$reports->expects('save')->never();
// error_log() is a PHP internal and cannot be stubbed; redirect it to a temp file instead.
$log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$previous = ini_set('error_log', $log);
try {
$response = (new DiviCheckRoute(static fn () => $check, $reports))->handle();
} finally {
ini_set('error_log', (string) $previous);
}
self::assertStringContainsString('lit_mcp_notice=divi_check_failed', $response->get_headers()['Location']);
self::assertStringContainsString(
'divi check failed: RuntimeException: divi kaputt',
(string) file_get_contents($log)
);
unlink($log);
}
}