'divi', 'lit/divi-outline' => 'divi', 'lit/library-save' => 'divi-library', 'lit/tb-create-template' => 'divi-theme-builder', 'lit/content-create' => 'wordpress', ]; private function ability(string $name, bool $readonly): Ability { $ability = Mockery::mock(Ability::class); $ability->allows('name')->andReturn($name); $ability->allows('annotations')->andReturn(['readonly' => $readonly]); return $ability; } private function gate(?FakeChangeLog $log = null): DiviToolGate { return new DiviToolGate(static fn (): array => self::GROUPS, $log); } private function untested(): void { ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0')); } public function testReleasedDiviRefusesNothingAndAddsNothing(): void { $gate = $this->gate(FakeChangeLog::forbidden()); self::assertNull($gate->refusal($this->ability('lit/divi-insert', false), [])); self::assertSame(['ok' => true], $gate->annotate($this->ability('lit/divi-outline', true), ['ok' => true])); } public function testWriteToolOfEveryDiviGroupIsRefused(): void { $this->untested(); $log = new FakeChangeLog(); $gate = $this->gate($log); foreach (['lit/divi-insert', 'lit/library-save', 'lit/tb-create-template'] as $name) { self::assertToolError($gate->refusal($this->ability($name, false), []), 'divi_untested', $name); } self::assertNull($gate->refusal($this->ability('lit/content-create', false), []), 'not a Divi tool (adapter lock)'); self::assertNull($gate->refusal($this->ability('lit/divi-outline', true), []), 'reading stays allowed'); self::assertSame(['refused', 'refused', 'refused'], array_column($log->records, 'result')); self::assertSame('Abgelehnt: Divi 5.3.0 ist nicht freigegeben.', $log->records[0]['summary']); } public function testReadResultsKeepTheirShapeAndGetTheNotice(): void { $this->untested(); $gate = $this->gate(); $read = $this->ability('lit/divi-outline', true); $notice = 'Achtung: Divi 5.3.0 ist für LIT MCP ungetestet; Lesen funktioniert, Schreiben ist gesperrt.'; self::assertSame(['post_id' => 7, 'warnings' => [$notice]], $gate->annotate($read, ['post_id' => 7])); self::assertSame( ['warnings' => ['alt', $notice]], $gate->annotate($read, ['warnings' => ['alt']]) ); self::assertSame([1, 2], $gate->annotate($read, [1, 2]), 'a list stays a list'); $error = new WP_Error('lit_not_found', 'x'); self::assertSame($error, $gate->annotate($read, $error)); $other = $this->ability('lit/content-create', true); self::assertSame(['post_id' => 7], $gate->annotate($other, ['post_id' => 7])); } public function testOutputSchemaAllowsWarnings(): void { $gate = $this->gate(); $read = $this->ability('lit/divi-outline', true); $properties = ['post_id' => ['type' => 'integer']]; $schema = $gate->outputSchema($read, ['type' => 'object', 'properties' => $properties]); $warnings = ['type' => 'array', 'items' => ['type' => 'string']]; self::assertSame($warnings, $schema['properties']['warnings'] ?? null); $own = ['type' => 'object', 'properties' => ['warnings' => ['type' => 'array']]]; self::assertSame($own, $gate->outputSchema($read, $own), 'an existing warnings property stays'); self::assertNull($gate->outputSchema($read, null)); self::assertSame( ['type' => 'object'], $gate->outputSchema($this->ability('lit/content-create', true), ['type' => 'object']) ); } }