fix: refuse private/unpublished menu links, write only changed menu items
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:
@@ -128,19 +128,77 @@ final class MenuSetItemsAbilityTest extends TestCase
|
||||
$items = [self::item(10, 0, 'Start'), self::item(11, 10, 'Team'), self::item(12, 0, 'Kontakt', 'custom', '/k')];
|
||||
$items[0]->url = 'https://example.test/';
|
||||
Functions\when('wp_get_nav_menu_items')->justReturn($items);
|
||||
Functions\when('wp_update_nav_menu_item')->alias(static fn (int $menu, int $id): int => $id);
|
||||
Functions\expect('wp_delete_post')->never();
|
||||
Functions\expect('wp_update_nav_menu_item')->never();
|
||||
$log = Mockery::mock(ChangeLog::class);
|
||||
$log->allows('record')->andReturn(23);
|
||||
$log->expects('record')->never();
|
||||
|
||||
$result = (new MenuSetItemsAbility(new MenuWriter(), new MenuOperations(), $log))->execute(
|
||||
['menu_id' => 3, 'items' => MenuTree::build($items)]
|
||||
);
|
||||
|
||||
self::assertStringContainsString('0 neu, 3 geändert, 0 entfernt', $result['summary']);
|
||||
self::assertSame('Menü "Hauptmenü": keine Änderung.', $result['summary']);
|
||||
self::assertSame([], $result['changed']);
|
||||
self::assertNull($result['log_id']);
|
||||
self::assertSame($result['previous_items'], $result['items']);
|
||||
}
|
||||
|
||||
public function testAllowUnpublishedIsPassedToThePlan(): void
|
||||
{
|
||||
$writer = Mockery::mock(MenuWriter::class);
|
||||
$writer->expects('plan')->with(Mockery::type('array'), Mockery::type('array'), true)
|
||||
->andReturn(['errors' => ['items.0' => 'x'], 'removals' => [], 'warnings' => []]);
|
||||
$ability = new MenuSetItemsAbility($writer, new MenuOperations(), Mockery::mock(ChangeLog::class));
|
||||
|
||||
$result = $ability->execute(
|
||||
['menu_id' => 3, 'items' => [['type' => 'post', 'object_id' => 7]], 'allow_unpublished' => true]
|
||||
);
|
||||
|
||||
self::assertSame('lit_validation_failed', $result->get_error_code());
|
||||
self::assertArrayHasKey('allow_unpublished', $ability->inputSchema()['properties']);
|
||||
}
|
||||
|
||||
public function testADraftLinkIsRefusedWithoutTheFlagAndWrittenWithAWarningWithIt(): void
|
||||
{
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
Functions\when('get_post')->justReturn(
|
||||
new \WP_Post(['ID' => 7, 'post_type' => 'page', 'post_status' => 'draft', 'post_title' => 'Neu'])
|
||||
);
|
||||
Functions\when('get_post_type')->justReturn('page');
|
||||
Functions\expect('wp_update_nav_menu_item')->once()->andReturn(60);
|
||||
$log = Mockery::mock(ChangeLog::class);
|
||||
$log->allows('record')->andReturn(24);
|
||||
$ability = new MenuSetItemsAbility(new MenuWriter(), new MenuOperations(), $log);
|
||||
$input = ['menu_id' => 3, 'operations' => [['op' => 'add', 'item' => ['type' => 'post', 'object_id' => 7]]]];
|
||||
|
||||
$refused = $ability->execute($input);
|
||||
$allowed = $ability->execute($input + ['allow_unpublished' => true]);
|
||||
|
||||
self::assertSame('lit_validation_failed', $refused->get_error_code());
|
||||
self::assertStringContainsString('allow_unpublished', $refused->get_error_data()['fields']['items.1']);
|
||||
self::assertStringContainsString('1 neu', $allowed['summary']);
|
||||
self::assertStringContainsString('sofort im Menü sichtbar', $allowed['warnings'][0]);
|
||||
}
|
||||
|
||||
public function testAPrivatePostIsNeverLinked(): void
|
||||
{
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
Functions\when('get_post')->justReturn(
|
||||
new \WP_Post(['ID' => 7, 'post_type' => 'page', 'post_status' => 'private', 'post_title' => 'Intern'])
|
||||
);
|
||||
Functions\expect('wp_update_nav_menu_item')->never();
|
||||
$ability = new MenuSetItemsAbility(new MenuWriter(), new MenuOperations(), Mockery::mock(ChangeLog::class));
|
||||
|
||||
$result = $ability->execute([
|
||||
'menu_id' => 3,
|
||||
'allow_unpublished' => true,
|
||||
'operations' => [['op' => 'add', 'item' => ['type' => 'post', 'object_id' => 7]]],
|
||||
]);
|
||||
|
||||
self::assertSame('lit_validation_failed', $result->get_error_code());
|
||||
self::assertStringContainsString('Private Inhalte', $result->get_error_data()['fields']['items.1']);
|
||||
}
|
||||
|
||||
public function testAWriteFailureIsPassedOnAsWriteFailed(): void
|
||||
{
|
||||
$writer = Mockery::mock(MenuWriter::class);
|
||||
@@ -190,5 +248,6 @@ final class MenuSetItemsAbilityTest extends TestCase
|
||||
self::assertSame(['readonly' => false, 'destructive' => true, 'idempotent' => false], $ability->annotations());
|
||||
self::assertStringContainsString('confirm: true', $ability->description());
|
||||
self::assertStringContainsString('nur nötig', $ability->description());
|
||||
self::assertStringContainsString('CSS-Klassen', $ability->description());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user