fix: menu rollback survives a throwing delete of a new item (S50)

MenuWriter::fail guards wp_delete_post for created items: the touched items
are still restored and the client gets partial_write (with previous_items)
instead of internal_error.

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:
2026-09-26 18:35:21 +02:00
co-authored by Claude Opus 5.5
parent eda592548e
commit 267e5d2076
2 changed files with 38 additions and 2 deletions
@@ -345,6 +345,37 @@ final class MenuWriterTest extends TestCase
self::assertCount(4, $calls);
}
/** Ruling S50: a throwing delete of a new item does not stop the rollback; the result is partial_write. */
public function testAThrowingDeleteOfANewItemStillRestoresTheRestAndIsPartial(): void
{
$calls = [];
Functions\when('wp_update_nav_menu_item')->alias(static function (int $menu, int $id, array $args) use (&$calls) {
$calls[] = [$id, $args];
return count($calls) === 3 ? new WP_Error('db', 'kaputt') : ($id > 0 ? $id : 50);
});
Functions\expect('wp_delete_post')->once()->with(50, true)->andThrow(new \RuntimeException('hook kaputt'));
try {
(new MenuWriter())->apply(3, [
['id' => 11, 'title' => 'Wir', 'children' => [
['type' => 'custom', 'url' => '/a', 'title' => 'A'],
['type' => 'custom', 'url' => '/b', 'title' => 'B'],
]],
], $this->current());
self::fail('WriteFailed expected');
} catch (WriteFailed $e) {
self::assertFalse($e->rolledBack);
self::assertStringContainsString('kaputt', $e->getMessage());
self::assertStringContainsString('created item 50 not deleted: hook kaputt', $e->getMessage());
$error = $e->toError();
self::assertToolError($error, 'partial_write');
self::assertSame('Home', $error->get_error_data()['previous_items'][0]['title']);
}
self::assertSame(11, $calls[3][0], 'the touched item is restored after the failed delete');
self::assertCount(4, $calls);
}
public function testAFailedRemovalAfterAnotherRemovalIsAPartialWrite(): void
{
Functions\when('wp_update_nav_menu_item')->alias(static fn (int $menu, int $id): int => $id);