translation_owner-1.0.1/tests/src/Unit/TranslationOwnerCommandsTest.php

tests/src/Unit/TranslationOwnerCommandsTest.php
<?php

namespace Drupal\Tests\translation_owner\Unit;

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\NodeInterface;
use Drupal\translation_owner\Commands\TranslationOwnerCommands;
use Drupal\user\UserInterface;
use Drupal\Tests\UnitTestCase;
use Psr\Log\LoggerInterface;

/**
 * Unit tests for TranslationOwnerCommands.
 *
 * @group translation_owner
 */
class TranslationOwnerCommandsTest extends UnitTestCase {

  /**
   * The mocked entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $entityTypeManager;

  /**
   * The mocked database connection.
   *
   * @var \Drupal\Core\Database\Connection|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $database;

  /**
   * The translation owner commands service.
   *
   * @var \Drupal\translation_owner\Commands\TranslationOwnerCommands
   */
  protected $commands;

  /**
   * The mocked node storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $nodeStorage;

  /**
   * The mocked user storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $userStorage;

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

    $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
    $this->database = $this->getMockBuilder('Drupal\Core\Database\Connection')
      ->disableOriginalConstructor()
      ->getMock();

    $this->nodeStorage = $this->createMock(EntityStorageInterface::class);
    $this->userStorage = $this->createMock(EntityStorageInterface::class);

    $this->entityTypeManager
      ->method('getStorage')
      ->willReturnMap([
        ['node', $this->nodeStorage],
        ['user', $this->userStorage],
      ]);

    $this->commands = new TranslationOwnerCommands(
      $this->entityTypeManager,
      $this->database
    );

    // Mock the logger to prevent errors in tests.
    $logger = $this->createMock(LoggerInterface::class);
    $this->commands->setLogger($logger);
  }

  /**
   * Tests that entity cache is properly cleared to prevent data duplication.
   *
   * This test specifically addresses the bug where processing multiple
   * translations of the same node would result in data duplication due to
   * stale entity cache.
   */
  public function testEntityCacheClearingPreventsDataDuplication() {
    // Test data simulating the bug scenario:
    // 123875,fr,28716
    // 123875,pt-pt,22265
    $nid = 123875;
    $first_langcode = 'fr';
    $first_uid = 28716;
    $second_langcode = 'pt-pt';
    $second_uid = 22265;

    // Mock users.
    $first_user = $this->createMock(UserInterface::class);
    $second_user = $this->createMock(UserInterface::class);

    $this->userStorage
      ->method('load')
      ->willReturnMap([
        [$first_uid, $first_user],
        [$second_uid, $second_user],
      ]);

    // Mock node with translations.
    $node = $this->createMock(NodeInterface::class);
    $french_translation = $this->createMock(NodeInterface::class);
    $portuguese_translation = $this->createMock(NodeInterface::class);

    // Set up node to return different translation objects.
    $node->method('hasTranslation')
      ->willReturnMap([
        [$first_langcode, TRUE],
        [$second_langcode, TRUE],
      ]);

    $node->method('getTranslation')
      ->willReturnMap([
        [$first_langcode, $french_translation],
        [$second_langcode, $portuguese_translation],
      ]);

    // Critical: Each translation should return its own UID.
    $french_translation->method('getOwnerId')->willReturn(1); // Original UID
    $portuguese_translation->method('getOwnerId')->willReturn(2); // Original UID

    // Mock node storage to always return fresh node instance.
    $this->nodeStorage->method('load')
      ->with($nid)
      ->willReturn($node);

    // Critical test: Verify resetCache is called to prevent stale data.
    $this->nodeStorage->expects($this->exactly(4))
      ->method('resetCache')
      ->with([$nid]);

    // Verify setOwnerId is called with correct values for each translation.
    $french_translation->expects($this->once())
      ->method('setOwnerId')
      ->with($first_uid);

    $portuguese_translation->expects($this->once())
      ->method('setOwnerId')
      ->with($second_uid);

    // Execute the operations that previously caused the bug.
    $this->commands->updateTranslationUid($nid, $first_langcode, $first_uid);
    $this->commands->updateTranslationUid($nid, $second_langcode, $second_uid);

    // If we reach here without the mock expectations failing,
    // the cache clearing is working correctly and preventing
    // the data duplication bug.
    $this->assertTrue(TRUE, 'Entity cache clearing prevents data duplication');
  }

  /**
   * Tests bulk update with multiple translations of same node.
   *
   * This test uses a temporary CSV file to simulate the exact scenario
   * that caused the original bug.
   */
  public function testBulkUpdateWithSameNodeDifferentTranslations() {
    // Create a temporary CSV file with the problematic data.
    $csv_content = "nid,langcode,new_uid\n123875,fr,28716\n123875,pt-pt,22265\n";
    $temp_file = tempnam(sys_get_temp_dir(), 'translation_test_');
    file_put_contents($temp_file, $csv_content);

    // Mock the same setup as previous test.
    $nid = 123875;
    
    $first_user = $this->createMock(UserInterface::class);
    $second_user = $this->createMock(UserInterface::class);

    $this->userStorage->method('load')
      ->willReturnMap([
        [28716, $first_user],
        [22265, $second_user],
      ]);

    $node = $this->createMock(NodeInterface::class);
    $french_translation = $this->createMock(NodeInterface::class);
    $portuguese_translation = $this->createMock(NodeInterface::class);

    $node->method('hasTranslation')->willReturn(TRUE);
    $node->method('getTranslation')
      ->willReturnMap([
        ['fr', $french_translation],
        ['pt-pt', $portuguese_translation],
      ]);

    $french_translation->method('getOwnerId')->willReturn(1);
    $portuguese_translation->method('getOwnerId')->willReturn(2);

    $this->nodeStorage->method('load')->willReturn($node);

    // Critical: Verify cache is cleared 4 times (2 operations × 2 cache clears each).
    $this->nodeStorage->expects($this->exactly(4))
      ->method('resetCache')
      ->with([$nid]);

    // Verify each translation gets the correct UID.
    $french_translation->expects($this->once())
      ->method('setOwnerId')
      ->with(28716);

    $portuguese_translation->expects($this->once())
      ->method('setOwnerId')
      ->with(22265); // This should NOT be 28716!

    // Execute bulk update.
    $this->commands->bulkUpdateTranslationOwners($temp_file);

    // Clean up.
    unlink($temp_file);

    $this->assertTrue(TRUE, 'Bulk update correctly handles multiple translations');
  }

}

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

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