fix: log partial menu writes; describe that the link gate covers new items only
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -8,6 +8,7 @@ use LIT\MCP\Abilities\Ability;
|
|||||||
use LIT\MCP\Abilities\Errors;
|
use LIT\MCP\Abilities\Errors;
|
||||||
use LIT\MCP\Abilities\FollowUp;
|
use LIT\MCP\Abilities\FollowUp;
|
||||||
use LIT\MCP\Abilities\ToolResult;
|
use LIT\MCP\Abilities\ToolResult;
|
||||||
|
use LIT\MCP\Abilities\WriteFailed;
|
||||||
use LIT\MCP\ChangeLog\ChangeLog;
|
use LIT\MCP\ChangeLog\ChangeLog;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
@@ -50,7 +51,8 @@ final class MenuSetItemsAbility implements Ability
|
|||||||
. '#anker, http(s), mailto, tel) und title, jeweils optional children. Verlinkt werden nur für dich '
|
. '#anker, http(s), mailto, tel) und title, jeweils optional children. Verlinkt werden nur für dich '
|
||||||
. 'lesbare, veröffentlichte Inhalte und Begriffe öffentlicher Taxonomien; private Inhalte nie. '
|
. 'lesbare, veröffentlichte Inhalte und Begriffe öffentlicher Taxonomien; private Inhalte nie. '
|
||||||
. 'Entwürfe/ausstehende/geplante Inhalte nur mit allow_unpublished: true – WordPress zeigt sie sofort '
|
. 'Entwürfe/ausstehende/geplante Inhalte nur mit allow_unpublished: true – WordPress zeigt sie sofort '
|
||||||
. 'im Menü, Besucher erhalten bis zur Veröffentlichung einen toten Link. Das '
|
. 'im Menü, Besucher erhalten bis zur Veröffentlichung einen toten Link. Diese Prüfung gilt nur für '
|
||||||
|
. 'neu verlinkte Einträge; bestehende Einträge bleiben unabhängig vom Status ihres Ziels erhalten. Das '
|
||||||
. 'Ziel eines bestehenden Eintrags ändern: entfernen und neu anlegen. Alles wird vorab geprüft, dann '
|
. 'Ziel eines bestehenden Eintrags ändern: entfernen und neu anlegen. Alles wird vorab geprüft, dann '
|
||||||
. 'ganz oder gar nicht geschrieben. confirm: true ist nur nötig, wenn dabei Einträge wegfallen – '
|
. 'ganz oder gar nicht geschrieben. confirm: true ist nur nötig, wenn dabei Einträge wegfallen – '
|
||||||
. 'Menüeinträge haben keinen Papierkorb und werden endgültig gelöscht. Die Antwort enthält '
|
. 'Menüeinträge haben keinen Papierkorb und werden endgültig gelöscht. Die Antwort enthält '
|
||||||
@@ -166,7 +168,19 @@ final class MenuSetItemsAbility implements Ability
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Throws WriteFailed (after its own rollback) – the registrar turns it into the error.
|
// Throws WriteFailed (after its own rollback) – the registrar turns it into the error.
|
||||||
$stats = $this->writer->apply($menuId, $tree, $current);
|
try {
|
||||||
|
$stats = $this->writer->apply($menuId, $tree, $current);
|
||||||
|
} catch (WriteFailed $e) {
|
||||||
|
if (! $e->rolledBack) {
|
||||||
|
$this->log->recordPartial(
|
||||||
|
$this->name(),
|
||||||
|
'nav_menu',
|
||||||
|
(string) $menuId,
|
||||||
|
sprintf('Menü "%s": Änderung nur teilweise zurückgenommen', $menu->name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
// The menu is saved – from here on failures are logged and reported as warnings only.
|
// The menu is saved – from here on failures are logged and reported as warnings only.
|
||||||
$warnings = $plan['warnings'];
|
$warnings = $plan['warnings'];
|
||||||
|
|||||||
@@ -204,7 +204,22 @@ final class MenuSetItemsAbilityTest extends TestCase
|
|||||||
$writer = Mockery::mock(MenuWriter::class);
|
$writer = Mockery::mock(MenuWriter::class);
|
||||||
$writer->allows('plan')->andReturn(['errors' => [], 'removals' => [], 'warnings' => []]);
|
$writer->allows('plan')->andReturn(['errors' => [], 'removals' => [], 'warnings' => []]);
|
||||||
$writer->expects('apply')->andThrow(new WriteFailed('Menü nicht gespeichert.', true));
|
$writer->expects('apply')->andThrow(new WriteFailed('Menü nicht gespeichert.', true));
|
||||||
$ability = new MenuSetItemsAbility($writer, new MenuOperations(), Mockery::mock(ChangeLog::class));
|
$log = Mockery::mock(ChangeLog::class);
|
||||||
|
$log->shouldNotReceive('recordPartial');
|
||||||
|
$ability = new MenuSetItemsAbility($writer, new MenuOperations(), $log);
|
||||||
|
|
||||||
|
$this->expectException(WriteFailed::class);
|
||||||
|
$ability->execute(['menu_id' => 3, 'items' => [['id' => 10]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAPartialWriteIsLoggedAsPartial(): void
|
||||||
|
{
|
||||||
|
$writer = Mockery::mock(MenuWriter::class);
|
||||||
|
$writer->allows('plan')->andReturn(['errors' => [], 'removals' => [], 'warnings' => []]);
|
||||||
|
$writer->expects('apply')->andThrow(new WriteFailed('Menü nicht gespeichert.', false));
|
||||||
|
$log = Mockery::mock(ChangeLog::class);
|
||||||
|
$log->expects('recordPartial')->with('lit/menu-set-items', 'nav_menu', '3', Mockery::type('string'));
|
||||||
|
$ability = new MenuSetItemsAbility($writer, new MenuOperations(), $log);
|
||||||
|
|
||||||
$this->expectException(WriteFailed::class);
|
$this->expectException(WriteFailed::class);
|
||||||
$ability->execute(['menu_id' => 3, 'items' => [['id' => 10]]]);
|
$ability->execute(['menu_id' => 3, 'items' => [['id' => 10]]]);
|
||||||
@@ -249,5 +264,6 @@ final class MenuSetItemsAbilityTest extends TestCase
|
|||||||
self::assertStringContainsString('confirm: true', $ability->description());
|
self::assertStringContainsString('confirm: true', $ability->description());
|
||||||
self::assertStringContainsString('nur nötig', $ability->description());
|
self::assertStringContainsString('nur nötig', $ability->description());
|
||||||
self::assertStringContainsString('CSS-Klassen', $ability->description());
|
self::assertStringContainsString('CSS-Klassen', $ability->description());
|
||||||
|
self::assertStringContainsString('nur für neu verlinkte Einträge', $ability->description());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user