tests/Support/AssertsToolErrors (used by TestCase): assertToolError($result, code)
checks the WP_Error code lit_<code> and the code of the client JSON and returns the
payload; clientError() decodes the payload; errorFields() returns the fields and
asserts they exist. Replaces the private clientError() decoders of
DiviWriteAbilitiesTest/DiviReplaceTextAbilityTest, 216 single-line
assertSame('lit_…', …->get_error_code()) assertions, 24 get_error_data()['fields']
digs and two json_decode(message) reads. ErrorsTest keeps json_decode: it tests
the format itself. Tests 1404 -> 1409 (AssertsToolErrorsTest); no test has fewer
assertions.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
177 lines
7.6 KiB
PHP
177 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Media;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Abilities\Errors;
|
|
use LIT\MCP\Abilities\WriteFailed;
|
|
use LIT\MCP\ChangeLog\ChangeLog;
|
|
use LIT\MCP\Integrations\Media\MediaImporter;
|
|
use LIT\MCP\Integrations\Media\MediaPresenter;
|
|
use LIT\MCP\Integrations\Media\MediaUploadAbility;
|
|
use LIT\MCP\Integrations\Media\MediaUploadFromUrlAbility;
|
|
use LIT\MCP\Integrations\Media\MediaUploads;
|
|
use LIT\MCP\Limits;
|
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
|
use LIT\MCP\Tests\Support\SchemaFixture;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use RuntimeException;
|
|
use WP_Post;
|
|
|
|
final class MediaUploadAbilitiesTest extends TestCase
|
|
{
|
|
private function uploads(?ChangeLog $log = null): MediaUploads
|
|
{
|
|
$presenter = Mockery::mock(MediaPresenter::class);
|
|
$presenter->allows('summary')->andReturn(['id' => 55, 'title' => 'Logo']);
|
|
$schema = SchemaFixture::managed();
|
|
|
|
return new MediaUploads($presenter, $log ?? FakeChangeLog::forbidden(), $schema);
|
|
}
|
|
|
|
private function savedAttachment(): void
|
|
{
|
|
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 55, 'post_type' => 'attachment', 'post_title' => 'Logo']));
|
|
Functions\when('get_edit_post_link')->justReturn('https://kunde.de/wp-admin/post.php?post=55&action=edit');
|
|
}
|
|
|
|
public function testUploadFromUrlReportsTheNewAttachment(): void
|
|
{
|
|
$this->savedAttachment();
|
|
$importer = Mockery::mock(MediaImporter::class);
|
|
$importer->expects('fromUrl')->with('https://example.com/logo.png', null, ['alt' => 'Logo'])->andReturn(55);
|
|
$log = new FakeChangeLog(3);
|
|
|
|
$result = (new MediaUploadFromUrlAbility($importer, $this->uploads($log)))
|
|
->execute(['url' => 'https://example.com/logo.png', 'alt' => 'Logo']);
|
|
|
|
self::assertSame(['id' => 55, 'title' => 'Logo'], $result['attachment']);
|
|
self::assertSame(['id' => '55', 'type' => 'attachment', 'action' => 'created', 'label' => 'Logo'], $result['changed'][0]);
|
|
self::assertSame(3, $result['log_id']);
|
|
self::assertSame('https://kunde.de/wp-admin/post.php?post=55&action=edit', $result['edit_url']);
|
|
self::assertSame([], $result['warnings']);
|
|
$log->assertRecorded([
|
|
'ability' => 'lit/media-upload-from-url',
|
|
'object_type' => 'attachment',
|
|
'object_id' => '55',
|
|
]);
|
|
}
|
|
|
|
public function testBase64UploadPassesFilenameDataAndMeta(): void
|
|
{
|
|
$this->savedAttachment();
|
|
$importer = Mockery::mock(MediaImporter::class);
|
|
$importer->expects('fromBase64')->with('AAAA', 'x.png', ['title' => 'Logo', 'post_id' => 5])->andReturn(55);
|
|
$log = new FakeChangeLog(4);
|
|
|
|
$result = (new MediaUploadAbility($importer, $this->uploads($log), new Limits()))
|
|
->execute(['filename' => 'x.png', 'data' => 'AAAA', 'title' => 'Logo', 'post_id' => 5]);
|
|
|
|
self::assertTrue($result['ok']);
|
|
self::assertSame(4, $result['log_id']);
|
|
$log->assertRecorded(['ability' => 'lit/media-upload', 'object_type' => 'attachment', 'object_id' => '55']);
|
|
}
|
|
|
|
public function testAFailingChangeLogIsOnlyAWarning(): void
|
|
{
|
|
$this->savedAttachment();
|
|
$importer = Mockery::mock(MediaImporter::class);
|
|
$importer->allows('fromUrl')->andReturn(55);
|
|
$log = new FakeChangeLog(1, new RuntimeException('db down'));
|
|
// error_log() is a PHP internal and cannot be stubbed; redirect it to a temp file instead.
|
|
$file = (string) tempnam(sys_get_temp_dir(), 'lit-log');
|
|
$previous = ini_set('error_log', $file);
|
|
try {
|
|
$result = (new MediaUploadFromUrlAbility($importer, $this->uploads($log)))
|
|
->execute(['url' => 'https://example.com/logo.png']);
|
|
self::assertStringContainsString('db down', (string) file_get_contents($file));
|
|
} finally {
|
|
ini_set('error_log', (string) $previous);
|
|
unlink($file);
|
|
}
|
|
|
|
self::assertTrue($result['ok']);
|
|
self::assertNull($result['log_id']);
|
|
self::assertStringContainsString('Medium 55 ist gespeichert', $result['warnings'][0]);
|
|
}
|
|
|
|
public function testImporterErrorsArePassedThrough(): void
|
|
{
|
|
$importer = Mockery::mock(MediaImporter::class);
|
|
$importer->allows('fromBase64')->andReturn(Errors::invalidInput('zu groß'));
|
|
|
|
$result = (new MediaUploadAbility($importer, $this->uploads(), new Limits()))
|
|
->execute(['filename' => 'x.png', 'data' => 'AAAA']);
|
|
|
|
self::assertToolError($result, 'validation_failed');
|
|
}
|
|
|
|
public function testAPartialUploadIsLoggedAsPartialAndRethrown(): void
|
|
{
|
|
$failure = WriteFailed::partial('Alt-Text fehlt.', postId: 55);
|
|
$importer = Mockery::mock(MediaImporter::class);
|
|
$importer->allows('fromUrl')->andThrow($failure);
|
|
$log = FakeChangeLog::partialsOnly();
|
|
|
|
try {
|
|
(new MediaUploadFromUrlAbility($importer, $this->uploads($log)))->execute(['url' => 'https://example.com/a.png']);
|
|
self::fail('WriteFailed expected');
|
|
} catch (WriteFailed $e) {
|
|
self::assertSame($failure, $e);
|
|
}
|
|
$log->assertPartials([
|
|
'ability' => 'lit/media-upload-from-url',
|
|
'object_type' => 'attachment',
|
|
'object_id' => '55',
|
|
]);
|
|
}
|
|
|
|
public function testPermissionNeedsUploadRightsAndEditRightsOnTheParent(): void
|
|
{
|
|
Functions\when('current_user_can')->alias(static fn (string $cap, int $id = 0): bool => $cap === 'upload_files');
|
|
Functions\when('get_post')->justReturn(new WP_Post(['ID' => 5, 'post_type' => 'page', 'post_status' => 'publish']));
|
|
$ability = new MediaUploadFromUrlAbility(Mockery::mock(MediaImporter::class), $this->uploads());
|
|
|
|
self::assertTrue($ability->permission(['url' => 'https://example.com/a.png']));
|
|
self::assertToolError($ability->permission(['url' => 'https://example.com/a.png', 'post_id' => 5]), 'forbidden');
|
|
}
|
|
|
|
public function testPermissionRefusesUsersWithoutUploadRights(): void
|
|
{
|
|
Functions\when('current_user_can')->justReturn(false);
|
|
$ability = new MediaUploadAbility(Mockery::mock(MediaImporter::class), $this->uploads(), new Limits());
|
|
|
|
self::assertToolError($ability->permission(['filename' => 'a.png', 'data' => 'AAAA']), 'forbidden');
|
|
}
|
|
|
|
public function testUploadsForAParentThatDoesNotExistAreRefused(): void
|
|
{
|
|
Functions\when('current_user_can')->justReturn(true);
|
|
Functions\when('get_post')->justReturn(null);
|
|
$ability = new MediaUploadAbility(Mockery::mock(MediaImporter::class), $this->uploads(), new Limits());
|
|
|
|
self::assertToolError($ability->permission(['filename' => 'a.png', 'data' => 'AAAA', 'post_id' => 9]), 'not_found');
|
|
}
|
|
|
|
public function testDescriptionNamesTheConfiguredLimit(): void
|
|
{
|
|
Functions\when('get_option')->justReturn(['upload_mb' => 8]);
|
|
$ability = new MediaUploadAbility(Mockery::mock(MediaImporter::class), $this->uploads(), new Limits());
|
|
|
|
self::assertStringContainsString('8 MB', $ability->description());
|
|
self::assertFalse($ability->isDirect());
|
|
self::assertTrue((new MediaUploadFromUrlAbility(Mockery::mock(MediaImporter::class), $this->uploads()))->isDirect());
|
|
}
|
|
|
|
public function testDescriptionNamesTheTenMegabyteCapForBase64(): void
|
|
{
|
|
Functions\when('get_option')->justReturn(['upload_mb' => 32]);
|
|
$ability = new MediaUploadAbility(Mockery::mock(MediaImporter::class), $this->uploads(), new Limits());
|
|
|
|
self::assertStringContainsString('höchstens 10 MB', $ability->description());
|
|
}
|
|
}
|