Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
236 lines
9.0 KiB
PHP
236 lines
9.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||
|
||
use ArrayObject;
|
||
use Brain\Monkey\Functions;
|
||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||
use LIT\MCP\Integrations\Divi\Release\ConstantRequirement;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||
use LIT\MCP\Integrations\Divi\Release\ManifestCheck;
|
||
use LIT\MCP\Integrations\Divi\Release\Mirror;
|
||
use LIT\MCP\Integrations\Divi\Release\Problem;
|
||
use LIT\MCP\Integrations\Divi\Release\SignatureFingerprint;
|
||
use LIT\MCP\Tests\Unit\TestCase;
|
||
use RuntimeException;
|
||
|
||
final class ManifestCheckTest extends TestCase
|
||
{
|
||
private function check(?\Closure $current = null): ManifestCheck
|
||
{
|
||
$manifests = [
|
||
'A' => new Manifest(
|
||
Manifest::PAGES,
|
||
calls: [
|
||
new Call('strlen', 1),
|
||
new Call('lit_mcp_missing_function', 0),
|
||
new Call(ArrayObject::class . '::count', 0),
|
||
new Call('Missing\Klass::run', 0),
|
||
new Call('str_replace'),
|
||
],
|
||
constants: ['PHP_VERSION', 'LIT_MCP_MISSING_CONSTANT'],
|
||
postTypes: ['et_pb_layout'],
|
||
taxonomies: ['scope'],
|
||
blockTypes: ['divi/global-layout'],
|
||
mirrors: [
|
||
new Mirror('same', 'a.php', 'a', null, 'gleich'),
|
||
new Mirror('changed', 'b.php', 'b', null, 'geändert'),
|
||
new Mirror('unreadable', 'c.php', 'c', null, 'fehlt'),
|
||
new Mirror('no-expectation', 'd.php', 'd', null, 'ohne Soll'),
|
||
]
|
||
),
|
||
];
|
||
|
||
$currentHashes = ['same' => 'h1', 'changed' => 'hX', 'no-expectation' => 'h4'];
|
||
|
||
return new ManifestCheck(
|
||
$manifests,
|
||
['same' => 'h1', 'changed' => 'h2', 'unreadable' => 'h3'],
|
||
$current ?? static fn (Mirror $mirror): ?string => $currentHashes[$mirror->id] ?? null,
|
||
static fn (string $name): bool => false
|
||
);
|
||
}
|
||
|
||
public function testCodeChecksCoverExistenceAndArgumentCount(): void
|
||
{
|
||
self::assertSame(
|
||
[
|
||
'Seiten: strlen()',
|
||
'Seiten: lit_mcp_missing_function()',
|
||
'Seiten: ArrayObject::count()',
|
||
'Seiten: Klass::run()',
|
||
'Seiten: str_replace()',
|
||
'Seiten: Konstante PHP_VERSION',
|
||
'Seiten: Konstante LIT_MCP_MISSING_CONSTANT',
|
||
],
|
||
array_keys($this->check()->codeChecks())
|
||
);
|
||
self::assertSame(
|
||
[
|
||
'Seiten: lit_mcp_missing_function()',
|
||
'Seiten: Klass::run()',
|
||
'Seiten: Konstante LIT_MCP_MISSING_CONSTANT',
|
||
],
|
||
ManifestCheck::failed($this->check()->codeChecks())
|
||
);
|
||
}
|
||
|
||
public function testAcceptsChecksTheArgumentCountTheAdapterPasses(): void
|
||
{
|
||
self::assertTrue(ManifestCheck::accepts(new Call('strlen', 1)));
|
||
self::assertFalse(ManifestCheck::accepts(new Call('strlen', 2)), 'strlen takes one argument');
|
||
self::assertFalse(ManifestCheck::accepts(new Call('str_replace', 2)), 'str_replace needs three');
|
||
self::assertTrue(ManifestCheck::accepts(new Call('sprintf', 5)), 'variadic');
|
||
self::assertTrue(ManifestCheck::accepts(new Call('str_replace')), 'existence only');
|
||
}
|
||
|
||
public function testMirrorChecksCompareWithTheExpectedFingerprints(): void
|
||
{
|
||
$checks = $this->check()->mirrorChecks();
|
||
|
||
self::assertSame(
|
||
[
|
||
'Seiten: Nachbau von a() – gleich',
|
||
'Seiten: Nachbau von b() – geändert',
|
||
'Seiten: Nachbau von c() – fehlt',
|
||
'Seiten: Nachbau von d() – ohne Soll',
|
||
],
|
||
array_keys($checks)
|
||
);
|
||
self::assertSame(
|
||
[
|
||
'Seiten: Nachbau von b() – geändert',
|
||
'Seiten: Nachbau von c() – fehlt',
|
||
'Seiten: Nachbau von d() – ohne Soll',
|
||
],
|
||
ManifestCheck::failed($checks)
|
||
);
|
||
}
|
||
|
||
public function testUnreadableMirrorIsAFailedCheck(): void
|
||
{
|
||
$check = $this->check(static function (): ?string {
|
||
throw new RuntimeException('Divi file gone');
|
||
});
|
||
|
||
self::assertCount(4, ManifestCheck::failed($check->mirrorChecks()));
|
||
}
|
||
|
||
public function testRegistrationChecksAskWordPressAndDivi(): void
|
||
{
|
||
Functions\when('post_type_exists')->justReturn(true);
|
||
Functions\when('taxonomy_exists')->justReturn(false);
|
||
|
||
$checks = $this->check()->registrationChecks();
|
||
|
||
self::assertSame(
|
||
['Seiten: Post-Typ et_pb_layout', 'Seiten: Taxonomie scope', 'Seiten: Block-Typ divi/global-layout'],
|
||
array_keys($checks)
|
||
);
|
||
self::assertSame(
|
||
['Seiten: Taxonomie scope', 'Seiten: Block-Typ divi/global-layout'],
|
||
ManifestCheck::failed($checks)
|
||
);
|
||
}
|
||
|
||
/** A manifest with one requirement per problem kind; the default feature is page-read. */
|
||
private function featureCheck(?\Closure $current = null): ManifestCheck
|
||
{
|
||
$manifest = new Manifest(
|
||
Manifest::PAGES,
|
||
calls: [
|
||
new Call('strlen', 1),
|
||
new Call('str_replace', 3, [DiviFeature::PageWrite]),
|
||
new Call('trim', 1),
|
||
new Call('lit_mcp_missing_function', 0, [DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite]),
|
||
],
|
||
constants: [
|
||
new ConstantRequirement('PHP_VERSION'),
|
||
new ConstantRequirement('LIT_MCP_MISSING_CONSTANT', [DiviFeature::Catalog]),
|
||
],
|
||
postTypes: ['lit_mcp_missing_type'],
|
||
mirrors: [
|
||
new Mirror('same', 'a.php', 'a', null, 'gleich'),
|
||
new Mirror('changed', 'b.php', 'b', null, 'geändert', [DiviFeature::ThemeBuilderWrite]),
|
||
new Mirror('no-expectation', 'd.php', 'd', null, 'ohne Soll'),
|
||
],
|
||
features: [DiviFeature::PageRead]
|
||
);
|
||
$hashes = ['same' => 'h1', 'changed' => 'hX', 'no-expectation' => 'h4'];
|
||
|
||
return new ManifestCheck(
|
||
['A' => $manifest],
|
||
[
|
||
'same' => 'h1',
|
||
'changed' => 'h2',
|
||
'sig:strlen' => (string) SignatureFingerprint::of(new Call('strlen', 1)),
|
||
'sig:str_replace' => 'a different signature',
|
||
],
|
||
$current ?? static fn (Mirror $mirror): ?string => $hashes[$mirror->id] ?? null,
|
||
static fn (string $name): bool => false
|
||
);
|
||
}
|
||
|
||
/**
|
||
* @param list<Problem> $problems
|
||
* @return list<array{label: string, kind: string, features: list<string>}>
|
||
*/
|
||
private static function rows(array $problems): array
|
||
{
|
||
return array_map(static fn (Problem $problem): array => $problem->toArray(), $problems);
|
||
}
|
||
|
||
public function testCodeProblemsNameKindAndFeaturesOfEveryFailedCall(): void
|
||
{
|
||
self::assertSame(
|
||
[
|
||
['label' => 'Seiten: str_replace() – Signatur geändert', 'kind' => 'signature',
|
||
'features' => ['page-write']],
|
||
['label' => 'Seiten: trim() – Referenz der Signatur fehlt', 'kind' => 'reference-missing',
|
||
'features' => ['page-read']],
|
||
['label' => 'Seiten: lit_mcp_missing_function()', 'kind' => 'call',
|
||
'features' => ['page-write', 'tb-write']],
|
||
['label' => 'Seiten: Konstante LIT_MCP_MISSING_CONSTANT', 'kind' => 'constant',
|
||
'features' => ['catalog']],
|
||
],
|
||
self::rows($this->featureCheck(static function (): never {
|
||
self::fail('code problems never read Divi files');
|
||
})->codeProblems())
|
||
);
|
||
}
|
||
|
||
public function testAMirrorWithoutReferenceIsAMirrorProblemLikeAChangedOne(): void
|
||
{
|
||
self::assertSame(
|
||
[
|
||
['label' => 'Seiten: Nachbau von b() – geändert', 'kind' => 'mirror', 'features' => ['tb-write']],
|
||
['label' => 'Seiten: Nachbau von d() – ohne Soll', 'kind' => 'mirror', 'features' => ['page-read']],
|
||
],
|
||
self::rows($this->featureCheck()->mirrorProblems())
|
||
);
|
||
}
|
||
|
||
public function testAnUnreadableMirrorIsAMirrorProblem(): void
|
||
{
|
||
$problems = $this->featureCheck(static function (): never {
|
||
throw new RuntimeException('Divi file gone');
|
||
})->mirrorProblems();
|
||
|
||
self::assertSame(['mirror', 'mirror', 'mirror'], array_column(self::rows($problems), 'kind'));
|
||
}
|
||
|
||
public function testProblemsAreCodeThenMirrorsAndRegistrationIsNone(): void
|
||
{
|
||
$check = $this->featureCheck();
|
||
|
||
self::assertEquals(array_merge($check->codeProblems(), $check->mirrorProblems()), $check->problems());
|
||
self::assertNotContains(
|
||
'Seiten: Post-Typ lit_mcp_missing_type',
|
||
array_column(self::rows($check->problems()), 'label')
|
||
);
|
||
}
|
||
}
|