Files
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

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);
}
}