entity_mesh-1.1.1/tests/src/Kernel/EntityMeshProcessingModeTest.php

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

namespace Drupal\Tests\entity_mesh\Kernel;

use Drupal\entity_mesh\TrackerInterface;
use Drupal\filter\Entity\FilterFormat;
use Drupal\node\Entity\Node;

/**
 * Tests the synchronous/asynchronous processing mode functionality.
 *
 * @group entity_mesh
 */
class EntityMeshProcessingModeTest extends EntityMeshTestBase {

  /**
   * The entity mesh render service.
   *
   * @var \Drupal\entity_mesh\EntityRender
   */
  protected $entityMeshRender;

  /**
   * The queue factory service.
   *
   * @var \Drupal\Core\Queue\QueueFactory
   */
  protected $queueFactory;

  /**
   * The queue for entity mesh.
   *
   * @var \Drupal\Core\Queue\QueueInterface
   */
  protected $queue;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    // Get services - these should be available after parent::setUp().
    $this->entityMeshRender = $this->container->get('entity_mesh.entity_render');
    $this->queueFactory = $this->container->get('queue');
    $this->queue = $this->queueFactory->get('entity_mesh_queue_worker');

    // Create a content type if it doesn't exist.
    $types = $this->container->get('entity_type.manager')
      ->getStorage('node_type')
      ->loadMultiple();
    if (!isset($types['article'])) {
      $this->createContentType(['type' => 'article', 'name' => 'Article']);
    }

    // The parent class already creates basic_html, but ensure other formats
    // exist.
    $additional_formats = ['plain_text', 'full_html'];
    foreach ($additional_formats as $format_id) {
      if (!FilterFormat::load($format_id)) {
        FilterFormat::create([
          'format' => $format_id,
          'name' => ucfirst(str_replace('_', ' ', $format_id)),
          'weight' => 0,
          'filters' => [],
        ])->save();
      }
    }

    // Clear any cached entity_mesh data.
    $this->container->get('cache.default')->deleteAll();

    // Ensure hooks are discovered.
    $this->container->get('module_handler')->resetImplementations();
  }

  /**
   * {@inheritdoc}
   */
  protected function createExampleNodes() {
    // This test doesn't use the standard test nodes from the base class.
    // We create specific nodes for each test method.
  }

  /**
   * {@inheritdoc}
   */
  public static function linkCasesProvider() {
    // This test doesn't use the data provider pattern from the base class.
    // Return minimal data to satisfy the parent method signature.
    return [[
      'source_entity_id' => 1,
      'source_entity_langcode' => 'en',
      'target_href' => '/test',
      'expected_target_link_type' => 'internal',
    ],
    ];
  }

  /**
   * Override parent testLinks to prevent it from running.
   *
   * This test is skipped because this test class focuses on processing modes.
   */
  public function testLinks(
    $source_entity_id = NULL,
    $source_entity_langcode = NULL,
    $target_href = NULL,
    $expected_target_link_type = NULL,
    $expected_target_subcategory = NULL,
    $expected_target_title = NULL,
    $expected_target_scheme = NULL,
    $expected_target_host = NULL,
    $expected_target_entity_type = NULL,
    $expected_target_entity_bundle = NULL,
    $expected_target_entity_id = NULL,
    $expected_target_entity_langcode = NULL,
    $expected_source_entity_type = NULL,
    $expected_source_entity_bundle = NULL,
    $expected_source_entity_langcode = NULL,
    $expected_source_title = NULL,
    $source_should_exist = TRUE,
  ) {
    // This test class doesn't use the parent's testLinks method.
    $this->markTestSkipped('This test is not applicable to processing mode tests.');
  }

  /**
   * Tests the countEntityLinks method.
   */
  public function testCountEntityLinks() {
    // Create a node with no links.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node without links',
      'body' => [
        'value' => 'This is a test node with no links.',
        'format' => 'plain_text',
      ],
    ]);
    $node->save();

    // Note: The node might have automatic links (like "Read more") added by
    // the theme. So we'll store the baseline count.
    $baseline_count = $this->entityMeshRender->countEntityLinks($node);
    $this->assertGreaterThanOrEqual(0, $baseline_count, 'Link count should be non-negative.');

    // Create a node with regular links.
    $node_with_links = Node::create([
      'type' => 'article',
      'title' => 'Test node with links',
      'body' => [
        'value' => 'This node has <a href="/node/100">one link</a> and <a href="/node/200">another link</a> and <a href="https://example.com">external link</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node_with_links->save();

    $count = $this->entityMeshRender->countEntityLinks($node_with_links);
    // The actual count should include any theme-added links plus our 3
    // explicit links.
    $this->assertGreaterThanOrEqual(3, $count, 'Node should have at least 3 links (our explicit links).');

    // Create a node with links and iframes.
    $node_with_mixed = Node::create([
      'type' => 'article',
      'title' => 'Test node with mixed content',
      'body' => [
        'value' => 'This has <a href="/node/100">a link</a> and <iframe src="/media/200"></iframe> and <iframe src="/node/300"></iframe>.',
        'format' => 'full_html',
      ],
    ]);
    $node_with_mixed->save();

    $count = $this->entityMeshRender->countEntityLinks($node_with_mixed);
    $this->assertGreaterThanOrEqual(3, $count, 'Node should have at least 3 links (1 link + 2 iframes).');

    // Test with duplicate links (should be counted as unique).
    $node_with_duplicates = Node::create([
      'type' => 'article',
      'title' => 'Test node with duplicate links',
      'body' => [
        'value' => 'This has <a href="/node/100">link 1</a> and <a href="/node/100">same link</a> and <a href="/node/200">link 2</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node_with_duplicates->save();

    $count = $this->entityMeshRender->countEntityLinks($node_with_duplicates);
    // Should have baseline + 2 unique links (duplicate /node/1 counted once)
    $this->assertGreaterThanOrEqual(2, $count, 'Should have at least 2 unique links.');
  }

  /**
   * Tests synchronous processing mode with entities below the limit.
   */
  public function testSynchronousProcessingBelowLimit() {
    // Set configuration for synchronous mode with limit of 5.
    $config = $this->config('entity_mesh.settings');
    $config->set('processing_mode', 'synchronous');
    $config->set('synchronous_limit', 5);
    $config->save();

    // Clear the tracker table before testing.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Create target nodes first so internal links resolve properly.
    $target1 = Node::create([
      'type' => 'article',
      'title' => 'Target node 1',
      'body' => ['value' => 'Target 1', 'format' => 'plain_text'],
    ]);
    $target1->save();

    $target2 = Node::create([
      'type' => 'article',
      'title' => 'Target node 2',
      'body' => ['value' => 'Target 2', 'format' => 'plain_text'],
    ]);
    $target2->save();

    // Create a node with 3 links (below the limit).
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node with few links',
      'body' => [
        'value' => 'This has <a href="/node/' . $target1->id() . '">link 1</a> and <a href="/node/' . $target2->id() . '">link 2</a> and <a href="https://example.com">external link</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node->save();

    // The node should be processed synchronously and tracked with PROCESSED
    // status.
    $tracker_entry = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->fields('t')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node->id())
      ->execute()
      ->fetchObject();

    $this->assertNotFalse($tracker_entry, 'Node processed synchronously should be tracked.');
    $this->assertEquals(
      TrackerInterface::STATUS_PROCESSED,
      $tracker_entry->status,
      'Node should have PROCESSED status after synchronous processing.'
    );

    // Verify the entity mesh data was saved.
    $query = \Drupal::database()->select('entity_mesh', 'em')
      ->fields('em')
      ->condition('source_entity_type', 'node')
      ->condition('source_entity_id', $node->id());
    $results = $query->execute()->fetchAll();

    // We should have at least 3 links (could be more if theme adds links)
    $this->assertGreaterThanOrEqual(3, count($results), 'At least three targets should be saved in entity_mesh table.');
  }

  /**
   * Tests synchronous processing mode with entities above the limit.
   */
  public function testSynchronousProcessingAboveLimit() {
    // Set configuration for synchronous mode with limit of 3.
    $config = $this->config('entity_mesh.settings');
    $config->set('processing_mode', 'synchronous');
    $config->set('synchronous_limit', 3);
    $config->save();

    // Clear the tracker table before testing.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Create target nodes.
    for ($i = 1; $i <= 5; $i++) {
      $target = Node::create([
        'type' => 'article',
        'title' => "Target node $i",
        'body' => ['value' => "Target $i", 'format' => 'plain_text'],
      ]);
      $target->save();
    }

    // Create a node with 5 links (above the limit).
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node with many links',
      'body' => [
        'value' => 'Links: <a href="/node/1">1</a> <a href="/node/2">2</a> <a href="/node/3">3</a> <a href="/node/4">4</a> <a href="/node/5">5</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node->save();

    // The node should be added to tracker because it has more links than the
    // limit.
    $tracker_count = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node->id())
      ->countQuery()
      ->execute()
      ->fetchField();

    $this->assertEquals(1, $tracker_count, 'Node with links above limit should be tracked.');

    // Verify the tracker entry has the correct status.
    $tracker_entry = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->fields('t')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node->id())
      ->execute()
      ->fetchObject();

    $this->assertNotFalse($tracker_entry, 'Tracker entry should exist.');
    $this->assertEquals(
      TrackerInterface::STATUS_PENDING,
      $tracker_entry->status,
      'Tracker status should be PENDING.'
    );
    $this->assertEquals(
      TrackerInterface::OPERATION_PROCESS,
      $tracker_entry->operation,
      'Tracker operation should be PROCESS.'
    );
  }

  /**
   * Tests asynchronous processing mode.
   */
  public function testAsynchronousProcessing() {
    // Set configuration for asynchronous mode.
    $config = $this->config('entity_mesh.settings');
    $config->set('processing_mode', 'asynchronous');
    $config->save();

    // Clear the tracker table before testing.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Create a node with any number of links.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node for async',
      'body' => [
        'value' => 'This has <a href="/node/1">just one link</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node->save();

    // In async mode, all nodes should be tracked regardless of link count.
    $tracker_count = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node->id())
      ->countQuery()
      ->execute()
      ->fetchField();

    $this->assertEquals(1, $tracker_count, 'In async mode, all nodes should be tracked.');

    // Verify no data was saved synchronously.
    $query = \Drupal::database()->select('entity_mesh', 'em')
      ->fields('em')
      ->condition('source_entity_type', 'node')
      ->condition('source_entity_id', $node->id());
    $results = $query->execute()->fetchAll();

    $this->assertCount(0, $results, 'No targets should be saved synchronously in async mode.');
  }

  /**
   * Tests synchronous mode with exactly the limit number of links.
   */
  public function testSynchronousProcessingExactLimit() {
    // Set configuration for synchronous mode with limit of 3.
    $config = $this->config('entity_mesh.settings');
    $config->set('processing_mode', 'synchronous');
    $config->set('synchronous_limit', 3);
    $config->save();

    // Clear the tracker table before testing.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Create target nodes first.
    for ($i = 1; $i <= 3; $i++) {
      $target = Node::create([
        'type' => 'article',
        'title' => "Target node $i",
        'body' => ['value' => "Target $i", 'format' => 'plain_text'],
      ]);
      $target->save();
    }

    // Create a node with exactly 3 links (equal to the limit).
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node with exact limit',
      'body' => [
        'value' => 'Links: <a href="/node/1">1</a> <a href="/node/2">2</a> <a href="/node/3">3</a>.',
        'format' => 'basic_html',
      ],
    ]);
    $node->save();

    // Debug: check the actual link count - may have extra theme links.
    $link_count = $this->entityMeshRender->countEntityLinks($node);

    // If there are more than 3 links (due to theme additions), the node will
    // be tracked.
    if ($link_count > 3) {
      // The node should be tracked because it has more links than the limit.
      $this->assertGreaterThan(3, $link_count, 'Node has more than 3 links due to theme additions.');

      $tracker_count = \Drupal::database()->select('entity_mesh_tracker', 't')
        ->condition('entity_type', 'node')
        ->condition('entity_id', $node->id())
        ->countQuery()
        ->execute()
        ->fetchField();

      $this->assertEquals(1, $tracker_count, 'Node with more links than limit should be tracked.');
      // Skip the rest of the test.
      return;
    }

    // The node should be processed synchronously and tracked with PROCESSED
    // status when equal to limit.
    $tracker_entry = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->fields('t')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node->id())
      ->execute()
      ->fetchObject();

    $this->assertNotFalse($tracker_entry, 'Node with links equal to limit should be tracked.');
    $this->assertEquals(
      TrackerInterface::STATUS_PROCESSED,
      $tracker_entry->status,
      'Node should have PROCESSED status after synchronous processing.'
    );

    // Verify the entity mesh data was saved.
    $query = \Drupal::database()->select('entity_mesh', 'em')
      ->fields('em')
      ->condition('source_entity_type', 'node')
      ->condition('source_entity_id', $node->id());
    $results = $query->execute()->fetchAll();

    $this->assertGreaterThanOrEqual(3, count($results), 'At least three targets should be saved in entity_mesh table.');
  }

  /**
   * Tests that asynchronous node deletion adds entry to tracker table.
   *
   * When a node is deleted in asynchronous mode, it should be added to the
   * entity_mesh_tracker table with operation=DELETE and status=PENDING.
   */
  public function testAsynchronousNodeDeletion() {
    // Set configuration for asynchronous mode.
    $config = $this->config('entity_mesh.settings');
    $config->set('processing_mode', 'asynchronous');
    $config->save();

    // Clear the tracker table before testing.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Create a node.
    $node = Node::create([
      'type' => 'article',
      'title' => 'Test node to delete',
      'body' => [
        'value' => 'This node will be deleted to test tracker behavior.',
        'format' => 'plain_text',
      ],
    ]);
    $node->save();
    $node_id = $node->id();

    // Verify the node was tracked for processing on creation.
    $tracker_count = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node_id)
      ->condition('operation', TrackerInterface::OPERATION_PROCESS)
      ->countQuery()
      ->execute()
      ->fetchField();

    $this->assertEquals(1, $tracker_count, 'Node creation should add entry to tracker in async mode.');

    // Clear the tracker to focus on deletion behavior.
    \Drupal::database()->truncate('entity_mesh_tracker')->execute();

    // Delete the node.
    $node->delete();

    // Verify that the deletion was tracked.
    $tracker_entry = \Drupal::database()->select('entity_mesh_tracker', 't')
      ->fields('t')
      ->condition('entity_type', 'node')
      ->condition('entity_id', $node_id)
      ->execute()
      ->fetchObject();

    $this->assertNotFalse($tracker_entry, 'Node deletion should add entry to tracker table.');
    $this->assertEquals(
      TrackerInterface::OPERATION_DELETE,
      $tracker_entry->operation,
      'Tracker entry should have OPERATION_DELETE.'
    );
    $this->assertEquals(
      TrackerInterface::STATUS_PENDING,
      $tracker_entry->status,
      'Tracker entry should have STATUS_PENDING.'
    );
    $this->assertEquals('node', $tracker_entry->entity_type, 'Entity type should be node.');
    $this->assertEquals($node_id, $tracker_entry->entity_id, 'Entity ID should match deleted node.');
    $this->assertNotEmpty($tracker_entry->timestamp, 'Timestamp should be set.');
    $this->assertEquals(0, $tracker_entry->retry_count, 'Retry count should be 0 initially.');
  }

}

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

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