justReturn('uuid-1'); } /** @param list $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 $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 */ 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); } }