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>
164 lines
6.2 KiB
PHP
164 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Compatibility;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use Closure;
|
|
use LIT\MCP\Compatibility\CheckStep;
|
|
use LIT\MCP\Compatibility\Compatibility;
|
|
use LIT\MCP\Compatibility\CompatibilityCheck;
|
|
use LIT\MCP\Compatibility\Manifest;
|
|
use LIT\MCP\Compatibility\ManifestCheck;
|
|
use LIT\MCP\Compatibility\Mirror;
|
|
use LIT\MCP\Compatibility\Profile;
|
|
use LIT\MCP\Compatibility\Tier;
|
|
use LIT\MCP\Plugin;
|
|
use LIT\MCP\Tests\Support\FakeCapability;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use RuntimeException;
|
|
|
|
final class CompatibilityCheckTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
Functions\when('wp_generate_uuid4')->justReturn('uuid-1');
|
|
}
|
|
|
|
/** @param list<Manifest> $manifests */
|
|
private static function profile(array $manifests = [], ?Closure $extraSteps = null, ?Closure $stateKey = null): Profile
|
|
{
|
|
return new Profile(
|
|
id: 'fake',
|
|
label: 'Fake',
|
|
tier: Tier::LockOnFailure,
|
|
capabilities: [FakeCapability::Read],
|
|
manifests: $manifests,
|
|
version: static fn (): ?string => '2.1.0',
|
|
stateKey: $stateKey,
|
|
extraSteps: $extraSteps
|
|
);
|
|
}
|
|
|
|
private static function withMirrors(): Manifest
|
|
{
|
|
return new Manifest('Fake', mirrors: [new Mirror('m', 'f.php', 'fn', null, 'x')]);
|
|
}
|
|
|
|
/** @param array<string, Closure(): bool> $code */
|
|
private static function check(Profile $profile, array $code = [], array $mirrors = []): ManifestCheck
|
|
{
|
|
$check = Mockery::mock(ManifestCheck::class);
|
|
$check->allows('codeChecks')->andReturn($code);
|
|
$check->allows('registrationChecks')->andReturn([]);
|
|
$check->allows('mirrorChecks')->andReturn($mirrors);
|
|
|
|
return $check;
|
|
}
|
|
|
|
/** @return list<string> */
|
|
private static function keys(CompatibilityCheck $check): array
|
|
{
|
|
return array_map(static fn (CheckStep $step): string => $step->key, $check->run(7)->steps);
|
|
}
|
|
|
|
public function testAProfileWithoutMirrorsGetsOnlyTheManifestStep(): void
|
|
{
|
|
$profile = self::profile([new Manifest('Fake')]);
|
|
$report = (new CompatibilityCheck($profile, self::check($profile)))->run(7);
|
|
|
|
self::assertTrue($report->green());
|
|
self::assertSame(['manifest'], array_map(static fn (CheckStep $s): string => $s->key, $report->steps));
|
|
self::assertSame('Fake-Funktionen, Klassen und Registrierungen', $report->steps[0]->label());
|
|
self::assertSame(['uuid-1', 7, '2.1.0', Plugin::VERSION, ''], [
|
|
$report->id, $report->userId, $report->version, $report->pluginVersion, $report->stateKey,
|
|
]);
|
|
}
|
|
|
|
public function testAManifestWithMirrorsAddsTheFingerprintStep(): void
|
|
{
|
|
$profile = self::profile([self::withMirrors()], stateKey: static fn (string $v): string => 'key-' . $v);
|
|
$check = new CompatibilityCheck($profile, self::check($profile, [], ['TB: Nachbau von y()' => static fn (): bool => false]));
|
|
|
|
$report = $check->run(7);
|
|
|
|
self::assertSame(['manifest', 'mirrors'], array_map(static fn (CheckStep $s): string => $s->key, $report->steps));
|
|
self::assertSame(['TB: Nachbau von y()'], $report->step(CheckStep::MIRRORS)?->problems);
|
|
self::assertSame('Fingerabdrücke nachgebauter Fake-Funktionen', $report->step(CheckStep::MIRRORS)?->label());
|
|
self::assertSame('key-2.1.0', $report->stateKey);
|
|
self::assertFalse($report->green());
|
|
}
|
|
|
|
public function testExtraStepsFollowAndKnowWhetherTheManifestIsClean(): void
|
|
{
|
|
$seen = [];
|
|
$extra = static function (bool $clean) use (&$seen): array {
|
|
$seen[] = $clean;
|
|
|
|
return [new CheckStep('extra', [])];
|
|
};
|
|
$profile = self::profile([self::withMirrors()], $extra);
|
|
|
|
$clean = new CompatibilityCheck($profile, self::check($profile));
|
|
$broken = new CompatibilityCheck($profile, self::check($profile, ['Fake: f()' => static fn (): bool => false]));
|
|
|
|
self::assertSame(['manifest', 'mirrors', 'extra'], self::keys($clean));
|
|
self::assertSame(['manifest', 'mirrors', 'extra'], self::keys($broken));
|
|
self::assertSame([true, false], $seen);
|
|
}
|
|
|
|
public function testAThrowingManifestCheckIsARedStep(): void
|
|
{
|
|
$profile = self::profile([new Manifest('Fake')]);
|
|
$manifests = Mockery::mock(ManifestCheck::class);
|
|
$manifests->allows('codeChecks')->andThrow(new RuntimeException('kaputt'));
|
|
|
|
$log = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
|
|
$previous = ini_set('error_log', $log);
|
|
try {
|
|
$report = (new CompatibilityCheck($profile, $manifests))->run(7);
|
|
$logged = (string) file_get_contents($log);
|
|
} finally {
|
|
ini_set('error_log', (string) $previous);
|
|
unlink($log);
|
|
}
|
|
|
|
self::assertSame(
|
|
['Prüfschritt abgebrochen (Details im PHP-Error-Log).'],
|
|
$report->step(CheckStep::MANIFEST)?->problems
|
|
);
|
|
self::assertStringContainsString('kaputt', $logged, 'the exception text goes to the error log');
|
|
}
|
|
|
|
public function testForProfileUsesTheManifestCheckOfTheProfile(): void
|
|
{
|
|
$profile = new Profile(
|
|
id: 'fake',
|
|
label: 'Fake',
|
|
tier: Tier::ReportOnly,
|
|
capabilities: [FakeCapability::Read],
|
|
manifestCheck: static fn (): ManifestCheck => new ManifestCheck(
|
|
[new Manifest('Fake', [new \LIT\MCP\Compatibility\Call('lit_mcp_missing_fn', 0)])],
|
|
signatures: false
|
|
)
|
|
);
|
|
|
|
$report = CompatibilityCheck::forProfile($profile)->run(1);
|
|
|
|
self::assertSame(['Fake: lit_mcp_missing_fn()'], $report->step(CheckStep::MANIFEST)?->problems);
|
|
}
|
|
|
|
public function testCompatibilityChecksAProfileWithoutManifestCheckClosureByItsManifests(): void
|
|
{
|
|
$profile = self::profile([new Manifest('Fake', [new \LIT\MCP\Compatibility\Call('lit_mcp_missing_fn', 0)])]);
|
|
|
|
$report = (new Compatibility([$profile]))->check($profile, 3);
|
|
|
|
self::assertSame(3, $report->userId);
|
|
self::assertSame(['Fake: lit_mcp_missing_fn()'], $report->step(CheckStep::MANIFEST)?->problems);
|
|
}
|
|
}
|