Files
WP-LIT-MCP/tests/Unit/Integrations/WordPress/Settings/WpSettingsAbilitiesTest.php
T

197 lines
8.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\WordPress\Settings;
use Brain\Monkey\Functions;
use LIT\MCP\ChangeLog\ChangeLog;
use LIT\MCP\Integrations\WordPress\Settings\SettingsWhitelist;
use LIT\MCP\Integrations\WordPress\Settings\WpSettingsGetAbility;
use LIT\MCP\Integrations\WordPress\Settings\WpSettingsUpdateAbility;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class WpSettingsAbilitiesTest extends TestCase
{
protected function tearDown(): void
{
unset($GLOBALS['wp_rewrite']);
parent::tearDown();
}
public function testGetFiltersByGroup(): void
{
Functions\when('get_option')->justReturn('Kunde');
$result = (new WpSettingsGetAbility(new SettingsWhitelist()))->execute(['group' => 'discussion']);
self::assertSame(['default_comment_status', 'default_ping_status'], array_column($result['settings'], 'key'));
self::assertSame('discussion', $result['settings'][0]['group']);
}
public function testUpdateIsAllOrNothing(): void
{
Functions\expect('update_option')->never();
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), Mockery::mock(ChangeLog::class)))
->execute(['settings' => ['blogname' => 'Neu', 'admin_email' => 'x@y.de']]);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertArrayHasKey('admin_email', $result->get_error_data()['fields']);
}
public function testRejectsAGenuinelyUnknownKey(): void
{
Functions\expect('update_option')->never();
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), Mockery::mock(ChangeLog::class)))
->execute(['settings' => ['template' => 'twentyten']]);
self::assertSame('lit_validation_failed', $result->get_error_code());
self::assertArrayHasKey('template', $result->get_error_data()['fields']);
}
public function testUpdatesOnlyChangedValuesAndFlushesPermalinks(): void
{
// A mutating stub: writeOne() verifies a write via get_option() afterwards, so
// the stub must actually reflect what update_option()/set_permalink_structure()
// "wrote", not just return a fixed value.
$options = [
'blogname' => 'Alt',
'blogdescription' => 'Gleich',
'permalink_structure' => '',
];
Functions\when('get_option')->alias(static function (string $key) use (&$options) {
return $options[$key];
});
Functions\expect('update_option')->once()->with('blogname', 'Neu')
->andReturnUsing(static function (string $key, $value) use (&$options): bool {
$options[$key] = $value;
return true;
});
$rewrite = Mockery::mock();
$rewrite->expects('set_permalink_structure')->with('/%postname%/')
->andReturnUsing(static function (string $structure) use (&$options): void {
$options['permalink_structure'] = $structure;
});
$GLOBALS['wp_rewrite'] = $rewrite;
Functions\expect('flush_rewrite_rules')->once();
$log = Mockery::mock(ChangeLog::class);
$log->expects('record')->with('lit/wp-settings-update', 'option', 'blogname,permalink_structure', Mockery::type('string'))->andReturn(30);
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), $log))->execute(['settings' => [
'blogname' => 'Neu',
'blogdescription' => 'Gleich',
'permalink_structure' => '/%postname%/',
]]);
self::assertSame(['blogname', 'permalink_structure'], array_column($result['changed'], 'id'));
self::assertSame(['blogname' => 'Alt', 'blogdescription' => 'Gleich', 'permalink_structure' => ''], $result['previous']);
self::assertSame(30, $result['log_id']);
}
public function testDoesNotTreatAnAlreadyStoredValueAsAWriteFailure(): void
{
// update_option() returns false both for a genuine failure and for a value that
// already matches the stored option. WPLANG's public value "en_US" normalizes to
// WordPress' internal "" – if the "already set?" check compared the raw client
// input against SettingsWhitelist::current()'s aliased public form instead of the
// normalized value against the actual stored option, this looked like a change,
// update_option() returned false because nothing needed to change, and that false
// was wrongly read as a failure – rolling back the unrelated blogname change too.
Functions\when('get_option')->alias(static fn (string $key) => [
'blogname' => 'Alt',
'WPLANG' => '',
][$key]);
Functions\expect('update_option')->once()->with('blogname', 'Neu')->andReturn(true);
$log = Mockery::mock(ChangeLog::class);
$log->expects('record')->andReturn(31);
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), $log))->execute(['settings' => [
'blogname' => 'Neu',
'WPLANG' => 'en_US',
]]);
self::assertSame(['blogname'], array_column($result['changed'], 'id'));
self::assertSame(31, $result['log_id']);
}
public function testRestoresPreviousValuesWhenAWriteFailsPartway(): void
{
Functions\when('get_option')->alias(static fn (string $key) => [
'blogname' => 'Alt',
'blogdescription' => 'Vorher',
][$key]);
Functions\expect('update_option')->once()->with('blogname', 'Neu')->andReturn(true);
Functions\expect('update_option')->once()->with('blogdescription', 'Auch neu')->andReturn(false);
Functions\expect('update_option')->once()->with('blogname', 'Alt')->andReturn(true);
$log = Mockery::mock(ChangeLog::class);
$log->shouldNotReceive('record');
$file = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$previous = ini_set('error_log', $file);
try {
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), $log))->execute(['settings' => [
'blogname' => 'Neu',
'blogdescription' => 'Auch neu',
]]);
} finally {
ini_set('error_log', (string) $previous);
unlink($file);
}
self::assertSame('lit_write_failed', $result->get_error_code());
}
public function testReportsPartialWriteWhenRestoringAlsoFails(): void
{
// Stateful: the restore write must genuinely not take effect (stay "Neu") for
// isStored() to correctly report the rollback as incomplete.
$options = [
'blogname' => 'Alt',
'blogdescription' => 'Vorher',
];
Functions\when('get_option')->alias(static function (string $key) use (&$options) {
return $options[$key];
});
Functions\expect('update_option')->once()->with('blogname', 'Neu')
->andReturnUsing(static function (string $key, $value) use (&$options): bool {
$options[$key] = $value;
return true;
});
Functions\expect('update_option')->once()->with('blogdescription', 'Auch neu')->andReturn(false);
Functions\expect('update_option')->once()->with('blogname', 'Alt')->andReturn(false);
$log = Mockery::mock(ChangeLog::class);
$log->shouldNotReceive('record');
$file = (string) tempnam(sys_get_temp_dir(), 'lit-mcp-log');
$previous = ini_set('error_log', $file);
try {
$result = (new WpSettingsUpdateAbility(new SettingsWhitelist(), $log))->execute(['settings' => [
'blogname' => 'Neu',
'blogdescription' => 'Auch neu',
]]);
} finally {
ini_set('error_log', (string) $previous);
unlink($file);
}
self::assertSame('lit_partial_write', $result->get_error_code());
self::assertStringContainsString('blogname', $result->get_error_message());
}
public function testSettingsToolsNeedManageOptions(): void
{
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap !== 'manage_options');
self::assertSame('lit_forbidden', (new WpSettingsGetAbility(new SettingsWhitelist()))->permission([])->get_error_code());
self::assertSame(
'lit_forbidden',
(new WpSettingsUpdateAbility(new SettingsWhitelist(), Mockery::mock(ChangeLog::class)))->permission([])->get_error_code()
);
}
}