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');
}
}