inline_image_saver-1.0.x-dev/tests/src/Kernel/InlineImageValidationTest.php

tests/src/Kernel/InlineImageValidationTest.php
<?php

declare(strict_types=1);

namespace Drupal\Tests\inline_image_saver\Kernel;

use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\file\FileInterface;
use Drupal\inline_image_saver\Form\InlineImageSaverSettingsForm;
use Drupal\inline_image_saver\Struct\InlineImageError;
use Drupal\inline_image_saver\Plugin\Validation\Constraint\InlineImageSaverConstraint;
use Drupal\KernelTests\Core\File\FileTestBase;
use Drupal\node\NodeInterface;
use Drupal\Tests\inline_image_saver\Traits\InlineImageTestTrait;
use Drupal\Tests\inline_image_saver\Traits\SetupBaseUrlTrait;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;

/**
 * Tests the inline_image_saver module.
 */
class InlineImageValidationTest extends FileTestBase {

  use TestFileCreationTrait;
  use ContentTypeCreationTrait;
  use UserCreationTrait;
  use InlineImageTestTrait;
  use SetupBaseUrlTrait;

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'node',
    'field',
    'file',
    'text',
    'editor',
    'filter',
    'filter_test',
    'inline_image_saver',
  ];

  /**
   * The default module settings.
   */
  protected array $defaultModuleSettings;

  /**
   * Test cases for different validation scenarios.
   *
   * @var array<string, array{
   *   expected_error: InlineImageError,
   *   is_src_downloadable: bool,
   *   img_attributes: array<string, string>,
   *   settings_for_success: array<string, mixed>,
   *   settings_for_failure: array<string, mixed>
   * }>
   */
  protected array $validationTestCases;

  /**
   * Tracks the number of times each error type has been tested.
   *
   * @var array<string, int>
   */
  protected array $testedErrors;

  /**
   * The node type being tested.
   */
  protected string $nodeTypeId;

  /**
   * The markup field name being tested.
   */
  protected string $markupFieldName;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->setupBaseUrl();
    $this->installEntitySchema('node');
    $this->installSchema('node', 'node_access');
    $this->installEntitySchema('user');
    $this->installEntitySchema('file');
    $this->installSchema('file', 'file_usage');
    $this->installConfig(['node', 'filter', 'filter_test', 'inline_image_saver']);
    $this->processableFormatId = 'full_html';
    $this->setUpCurrentUser([], ["use text format $this->processableFormatId"]);
    $this->nodeTypeId = $this->createContentType()->id();
    $this->markupFieldName = 'body';
    $this->defaultModuleSettings = [
      'processable_formats' => [$this->processableFormatId],
      'validation_settings' => [
        'allow_if_downloadable' => FALSE,
        'allow_data_uri' => FALSE,
        'check_file_exists' => FALSE,
        'validate_url' => FALSE,
        'validate_url_query' => FALSE,
        'check_file_mime' => FALSE,
      ],
      'fallback_markup' => '<s>' . Html::escape('<img src="@src" alt="@alt" title="@title" data-entity-type="@data-entity-type" data-entity-uuid="@data-entity-uuid">') . '</s>',
      'create_revision' => FALSE,
      'revision_log' => $this->randomMachineName(),
      'skip_on_sync' => FALSE,
    ];
  }

  /**
   * Tests the validation.
   */
  public function testValidation(): void {
    $inline_image_saver = $this->container->get('inline_image_saver');
    $img_entity_type_id = 'file';
    $images = $this->getTestFiles('image');
    $file_storage = $this->container->get('entity_type.manager')->getStorage($img_entity_type_id);

    // Prepare valid image file.
    $img_file = $file_storage->create((array) $images[0]);
    $img_file->save();
    $this->assertInstanceOf(FileInterface::class, $img_file);
    $img_src = $img_file->createFileUrl();
    $img_uuid = $img_file->uuid();

    // Prepare invalid image file - deleted file.
    $missing_image_file = $file_storage->create((array) $images[1]);
    $missing_image_file->save();
    $this->assertInstanceOf(FileInterface::class, $missing_image_file);
    $this->container->get('file_system')->unlink($missing_image_file->getFileUri());

    // Prepare invalid image file - wrong MIME type.
    $non_image_file = $file_storage->create((array) $this->getTestFiles('text')[0]);
    $non_image_file->save();
    $this->assertInstanceOf(FileInterface::class, $non_image_file);

    $this->addValidationTestCase(
      InlineImageError::InvalidDataUriValue,
      img_attributes: ['src' => 'data:foo'],
      settings_for_failure: ['allow_data_uri' => FALSE],
      settings_for_success: ['allow_data_uri' => TRUE],
    );
    $this->addValidationTestCase(
      InlineImageError::UnsupportedDataUriMimeType,
      img_attributes: ['src' => 'data:image/gif;base64,foo'],
      settings_for_failure: ['allow_data_uri' => FALSE],
      settings_for_success: ['allow_data_uri' => TRUE],
    );
    $this->addValidationTestCase(
      InlineImageError::DataUriNotAllowed,
      img_attributes: ['src' => 'data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='],
      settings_for_failure: ['allow_data_uri' => FALSE],
      settings_for_success: ['allow_data_uri' => TRUE],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(InlineImageError::EmptyEntityTypeAttribute);
    $this->addValidationTestCase(
      InlineImageError::EmptyEntityTypeAttribute,
      img_attributes: ['src' => $img_src],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::UnsupportedEntityTypeAttribute,
      img_attributes: [
        'src' => $img_src,
        'data-entity-type' => $this->randomMachineName(),
      ],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::EmptyEntityUuid,
      img_attributes: [
        'src' => $img_src,
        'data-entity-type' => $img_entity_type_id,
      ],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::EntityNotFound,
      img_attributes: [
        'src' => $img_src,
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => 'foo',
      ],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::FileNotFound,
      img_attributes: [
        'src' => $img_src,
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => $missing_image_file->uuid(),
      ],
      settings_for_failure: ['check_file_exists' => TRUE],
      settings_for_success: ['check_file_exists' => FALSE],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::UrlHostMismatch,
      img_attributes: [
        'src' => "https://{$this->randomMachineName()}.com/$img_src",
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => $img_uuid,
      ],
      settings_for_failure: ['validate_url' => TRUE],
      settings_for_success: ['validate_url' => FALSE],
    );
    $this->addValidationTestCase(
      InlineImageError::UrlPathMismatch,
      img_attributes: [
        'src' => "$img_src/foo",
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => $img_uuid,
      ],
      settings_for_failure: ['validate_url' => TRUE],
      settings_for_success: ['validate_url' => FALSE],
    );
    $this->addValidationTestCase(
      InlineImageError::UrlQueryMismatch,
      img_attributes: [
        'src' => "$img_src?foo",
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => $img_uuid,
      ],
      settings_for_failure: [
        'validate_url' => TRUE,
        'validate_url_query' => TRUE,
      ],
      settings_for_success: [
        'validate_url' => TRUE,
        'validate_url_query' => FALSE,
      ],
      is_src_downloadable: TRUE,
    );
    $this->addValidationTestCase(
      InlineImageError::UnsupportedFileMime,
      img_attributes: [
        'src' => $non_image_file->createFileUrl(),
        'data-entity-type' => $img_entity_type_id,
        'data-entity-uuid' => $non_image_file->uuid(),
      ],
      settings_for_failure: ['check_file_mime' => TRUE],
      settings_for_success: ['check_file_mime' => FALSE],
    );
    $this->testedErrors[InlineImageError::Unknown->name] ??= 1;
    $this->assertCount(count(InlineImageError::cases()), $this->testedErrors, 'Untestable errors found.');

    $constraint = $this->container->get('validation.constraint')->create('InlineImageSaver', NULL);
    $this->assertInstanceOf(InlineImageSaverConstraint::class, $constraint);

    foreach ($this->validationTestCases as $case) {
      $value_prop = $this->createNodeWithImage($case['img_attributes'])->get($this->markupFieldName)->first();

      // Test validation.
      $settings = [
        'enable_validation' => TRUE,
        'validation_settings' => [
          'allow_if_downloadable' => FALSE,
        ],
        'enable_download' => TRUE,
      ];
      $this->updateModuleSettings($case['settings_for_failure'], $settings);
      $violations = $value_prop->validate();
      $this->assertCount(1, $violations);
      $error_name = $case['expected_error']->name;
      $expected_message = $constraint->{"message$error_name"};
      $actual_message = $violations->get(0)->getMessage();
      $this->assertInstanceOf(TranslatableMarkup::class, $actual_message);
      $this->assertEquals($expected_message, $actual_message->getUntranslatedString());

      // Test validation pass.
      if ($case['settings_for_success']) {
        $this->updateModuleSettings($case['settings_for_success'], $settings);
        $violations = $value_prop->validate();
        $this->assertCount(0, $violations);
      }

      // Test validation pass if downloadable.
      if ($case['is_src_downloadable']) {
        $this->updateModuleSettings($case['settings_for_failure'], $settings, [
          'validation_settings' => ['allow_if_downloadable' => TRUE],
        ]);
        $violations = $value_prop->validate();
        $this->assertCount(0, $violations);
      }
    }

    // Test that entities are not processed when syncing is enabled.
    $settings = $this->updateModuleSettings([
      'enable_validation' => TRUE,
      'validation_settings' => [
        'allow_if_downloadable' => TRUE,
        'allow_data_uri' => TRUE,
        'check_file_exists' => TRUE,
        'validate_url' => TRUE,
        'validate_url_query' => TRUE,
        'check_file_mime' => TRUE,
      ],
      'enable_download' => TRUE,
      'prefer_reuse_files' => FALSE,
      'enable_replace' => TRUE,
      'create_revision' => TRUE,
      'skip_on_sync' => TRUE,
    ]);
    $node = $this->createNodeWithImage(['src' => $img_src]);
    $value_prop = $node->get($this->markupFieldName)->first()->get('value');
    $body_expected = $value_prop->getString();
    $node->setSyncing(TRUE)->save();
    $this->assertEquals($body_expected, $value_prop->getString());
    $node->setSyncing(FALSE);

    // Test format exclusion logic.
    $format_prop = $node->get($this->markupFieldName)->first()->get('format');
    $format_prop->setValue('filtered_html');
    $node->save();
    $this->assertEquals($body_expected, $value_prop->getString());
    $format_prop->setValue($this->processableFormatId);

    // Test file download.
    $current_revision_id = $node->getRevisionId();
    $node->save();
    $img = $this->extractImageElement($value_prop->getString());
    $validation = $inline_image_saver->validateImage($img);
    $this->assertEquals(NULL, $validation->error);
    $this->assertInstanceOf(FileInterface::class, $validation->file);
    $this->assertTrue($validation->file->isPermanent());
    $actual_uuid = $validation->file->uuid();
    $this->assertEquals($actual_uuid, $img->getAttribute('data-entity-uuid'));
    $this->assertNotEquals($actual_uuid, $img_uuid);
    $this->assertEquals($validation->file->getEntityTypeId(), $img->getAttribute('data-entity-type'));
    $this->assertNotEquals($current_revision_id, $node->getRevisionId());
    $this->assertEquals($settings['revision_log'], $node->getRevisionLogMessage());

    // Test fallback replacement.
    $current_revision_id = $node->getRevisionId();
    $value_prop->setValue($this->createImageMarkup([
      'src' => $this->randomMachineName() . '&',
      'alt' => $this->randomMachineName() . '&',
      'title' => $this->randomMachineName() . '&',
      'data-entity-type' => $this->randomMachineName() . '&',
      'data-entity-uuid' => $this->randomMachineName() . '&',
    ]));
    $img = $this->extractImageElement($value_prop->getString());
    $node->save();
    $placeholders = $inline_image_saver->placeholdersFromElement($img);
    $fallback_markup = $inline_image_saver->processPlaceholders($settings['fallback_markup'], $placeholders);
    $this->assertStringContainsString(Html::normalize($fallback_markup), Html::normalize($value_prop->getString()));
    $this->assertNotEquals($current_revision_id, $node->getRevisionId());
    $this->assertEquals($settings['revision_log'], $node->getRevisionLogMessage());

    // Test file reuse.
    $settings['prefer_reuse_files'] = TRUE;
    $this->updateModuleSettings($settings);
    $value_prop->setValue($this->createImageMarkup(['src' => $img_src]));
    $node->save();
    $img = $this->extractImageElement($value_prop->getString());
    $this->assertEquals($img_uuid, $img->getAttribute('data-entity-uuid'));
  }

  /**
   * Adds a validation test case.
   */
  protected function addValidationTestCase(InlineImageError $expected_error, array $img_attributes = [], array $settings_for_failure = [], array $settings_for_success = [], bool $is_src_downloadable = FALSE): void {
    $error_name = $expected_error->name;
    $count = &$this->testedErrors[$error_name];
    $count++;
    $error_key = $error_name;
    if ($count > 1) {
      $error_key .= "_$count";
    }
    $img_attributes['alt'] ??= $error_name;
    $case = [
      'expected_error' => $expected_error,
      'img_attributes' => $img_attributes,
      'is_src_downloadable' => $is_src_downloadable,
    ];
    foreach ([
      'settings_for_failure' => $settings_for_failure,
      'settings_for_success' => $settings_for_success,
    ] as $type => $settings) {
      $case[$type] = [];
      foreach ($settings as $key => $value) {
        $case[$type]['validation_settings'][$key] = $value;
      }
    }
    $this->validationTestCases[$error_key] = $case;
  }

  /**
   * Creates a node with image markup.
   */
  protected function createNodeWithImage(array $attributes): NodeInterface {
    return $this->container->get('entity_type.manager')->getStorage('node')->create([
      'type' => $this->nodeTypeId,
      'title' => $this->randomMachineName(),
      $this->markupFieldName => [
        'value' => $this->createImageMarkup($attributes),
        'format' => $this->processableFormatId,
      ],
    ]);
  }

  /**
   * Updates the module settings.
   */
  protected function updateModuleSettings(array ...$settings): array {
    $main_settings = [
      'enable_validation',
      'enable_download',
    ];
    $settings = NestedArray::mergeDeep($this->defaultModuleSettings, ...$settings);
    $this->assertEqualsCanonicalizing(
      $main_settings,
      array_intersect($main_settings, array_keys($settings)),
      'The configuration must include the main settings.'
    );
    return $this->config(InlineImageSaverSettingsForm::CONFIG_NAME)
      ->setData($settings)
      ->save()
      ->get();
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc