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:
@@ -431,9 +431,14 @@ class MenuWriter
|
||||
{
|
||||
$problems = [];
|
||||
foreach ($this->created as $itemId) {
|
||||
// Ruling S50: a throwing delete (e.g. a hook) must not stop the rest of the rollback.
|
||||
try {
|
||||
if (! wp_delete_post($itemId, true) instanceof WP_Post) {
|
||||
$problems[] = sprintf('created item %d not deleted', $itemId);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$problems[] = sprintf('created item %d not deleted: %s', $itemId, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->touched as $itemId) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user