From d2eb0235ccea45707c168feab2dba3d9ec7f941f Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Wed, 10 May 2023 15:23:22 +0100
Subject: [PATCH] Issue #3130107 by stefanos.petrakis, rpayanm, smustgrave,
 jungle: Extend unit test coverage for LanguageNegotiationContentEntity

---
 .../LanguageNegotiationContentEntityTest.php  | 204 ++++++++++++++----
 .../LanguageNegotiationTestBase.php           |  34 +++
 2 files changed, 199 insertions(+), 39 deletions(-)
 create mode 100644 core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationTestBase.php

diff --git a/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationContentEntityTest.php b/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationContentEntityTest.php
index 907a92e683a5..0f26e1da12af 100644
--- a/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationContentEntityTest.php
+++ b/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationContentEntityTest.php
@@ -2,15 +2,18 @@
 
 namespace Drupal\Tests\language\Unit\Plugin\LanguageNegotiation;
 
-use Symfony\Component\HttpFoundation\Request;
 use Drupal\Core\Cache\Context\CacheContextsManager;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityTypeManager;
 use Drupal\Core\Language\LanguageInterface;
-use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Url;
 use Drupal\language\ConfigurableLanguageManagerInterface;
-use Drupal\Tests\UnitTestCase;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity;
+use Symfony\Component\HttpFoundation\ParameterBag;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\ServerBag;
+use Symfony\Component\Routing\Route;
 
 /**
  * Tests the LanguageNegotiationContentEntity plugin class.
@@ -19,7 +22,7 @@
  * @coversDefaultClass \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity
  * @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity
  */
-class LanguageNegotiationContentEntityTest extends UnitTestCase {
+class LanguageNegotiationContentEntityTest extends LanguageNegotiationTestBase {
 
   /**
    * An array of mock LanguageInterface objects.
@@ -36,11 +39,11 @@ class LanguageNegotiationContentEntityTest extends UnitTestCase {
   protected $languageManager;
 
   /**
-   * A mock object implementing the AccountInterface.
-   *
-   * @var \Drupal\Core\Session\AccountInterface
+   * {@inheritdoc}
    */
-  protected $user;
+  protected function getPluginClass(): string {
+    return LanguageNegotiationContentEntity::class;
+  }
 
   /**
    * {@inheritdoc}
@@ -53,32 +56,39 @@ protected function setUp(): void {
     $language_de->expects($this->any())
       ->method('getId')
       ->will($this->returnValue('de'));
+    $language_de->expects($this->any())
+      ->method('getName')
+      ->will($this->returnValue('German'));
     $language_en = $this->createMock(LanguageInterface::class);
     $language_en->expects($this->any())
       ->method('getId')
       ->will($this->returnValue('en'));
-    $languages = [
+    $language_en->expects($this->any())
+      ->method('getName')
+      ->will($this->returnValue('English'));
+    $this->languages = [
       'de' => $language_de,
       'en' => $language_en,
     ];
-    $this->languages = $languages;
 
-    $language_manager = $this->getMockBuilder(ConfigurableLanguageManagerInterface::class)
-      ->getMock();
+    $language_manager = $this->createMock(ConfigurableLanguageManagerInterface::class);
     $language_manager->expects($this->any())
       ->method('getLanguages')
-      ->will($this->returnValue($languages));
+      ->will($this->returnValue($this->languages));
+    $language_manager->expects($this->any())
+      ->method('getNativeLanguages')
+      ->will($this->returnValue($this->languages));
     $this->languageManager = $language_manager;
 
-    $this->user = $this->getMockBuilder(AccountInterface::class)
-      ->getMock();
+    $container = new ContainerBuilder();
 
-    $cache_contexts_manager = $this->getMockBuilder(CacheContextsManager::class)
-      ->disableOriginalConstructor()
-      ->getMock();
+    $cache_contexts_manager = $this->createMock(CacheContextsManager::class);
     $cache_contexts_manager->method('assertValidTokens')->willReturn(TRUE);
-    $container = new ContainerBuilder();
     $container->set('cache_contexts_manager', $cache_contexts_manager);
+
+    $entityTypeManager = $this->createMock(EntityTypeManager::class);
+    $container->set('entity_type.manager', $entityTypeManager);
+
     \Drupal::setContainer($container);
   }
 
@@ -86,32 +96,148 @@ protected function setUp(): void {
    * @covers ::getLangcode
    */
   public function testGetLangcode() {
-    $entityTypeManagerMock = $this->getMockBuilder(EntityTypeManager::class)
-      ->disableOriginalConstructor()
-      ->getMock();
-    $languageNegotiationContentEntity = new LanguageNegotiationContentEntity($entityTypeManagerMock);
-    $languageNegotiationContentEntity->setLanguageManager($this->languageManager);
+    $languageNegotiationContentEntity = $this->createLanguageNegotiationPlugin();
 
-    // Case 1: NULL request object argument.
-    $this->assertNull($languageNegotiationContentEntity->getLangcode());
+    // Case 1: Empty request.
+    $this->assertEquals(NULL, $languageNegotiationContentEntity->getLangcode());
 
-    // Case 2: A request object is available, but the languageManager is not
-    // set.
-    $request = Request::create('/foo', 'GET');
-    $this->assertNull($languageNegotiationContentEntity->getLangcode($request));
+    // Case 2: A request is available, but the languageManager is not set and
+    // the static::QUERY_PARAMETER is not provided as a named parameter.
+    $request = Request::create('/de/foo', 'GET');
+    $request->query = new ParameterBag();
+    $this->assertEquals(NULL, $languageNegotiationContentEntity->getLangcode($request));
 
-    // Case 3: A request object is available, but static::QUERY_PARAMETER is
-    // set to a non-enabled language.
-    $request = Request::create('/foo', 'GET',
-      [LanguageNegotiationContentEntity::QUERY_PARAMETER => 'it']);
+    // Case 3: A request is available, the languageManager is set, but the
+    // static::QUERY_PARAMETER is not provided as a named parameter.
+    $languageNegotiationContentEntity->setLanguageManager($this->languageManager);
+    $this->assertEquals(NULL, $languageNegotiationContentEntity->getLangcode($request));
+
+    // Case 4: A request is available, the languageManager is set and the
+    // static::QUERY_PARAMETER is provided as a named parameter.
+    $expectedLangcode = 'de';
+    $request->query->set(LanguageNegotiationContentEntity::QUERY_PARAMETER, $expectedLangcode);
+    $this->assertEquals($expectedLangcode, $languageNegotiationContentEntity->getLangcode($request));
+
+    // Case 5: A request is available, the languageManager is set and the
+    // static::QUERY_PARAMETER is provided as a named parameter with a given
+    // langcode that is not one of the system supported ones.
+    $unknownLangcode = 'xx';
+    $request->query->set(LanguageNegotiationContentEntity::QUERY_PARAMETER, $unknownLangcode);
     $this->assertNull($languageNegotiationContentEntity->getLangcode($request));
+  }
 
-    // Case 4: A request object is available and static::QUERY_PARAMETER is
-    // set to an enabled language.
-    $request = Request::create('/foo', 'GET',
-      [LanguageNegotiationContentEntity::QUERY_PARAMETER => 'de']);
-    $this->assertSame('de', $languageNegotiationContentEntity->getLangcode($request));
+  /**
+   * @covers ::processOutbound
+   */
+  public function testProcessOutbound() {
+
+    // Case 1: Not all processing conditions are met.
+    $languageNegotiationContentEntityMock = $this->createPartialMock($this->getPluginClass(),
+      ['hasLowerLanguageNegotiationWeight', 'meetsContentEntityRoutesCondition']);
+    $languageNegotiationContentEntityMock->expects($this->exactly(2))
+      ->method('hasLowerLanguageNegotiationWeight')
+      ->willReturnOnConsecutiveCalls(
+        FALSE,
+        TRUE
+      );
+    $languageNegotiationContentEntityMock->expects($this->once())
+      ->method('meetsContentEntityRoutesCondition')
+      ->willReturnOnConsecutiveCalls(
+        FALSE
+      );
+    $options = [];
+    $path = $this->randomMachineName();
+
+    // Case 1a: Empty request.
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path));
+    $request = Request::create('/foo', 'GET');
+    $request->server = new ServerBag();
+    // Case 1b: Missing the route key in $options.
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request));
+    $options = ['route' => $this->createMock(Route::class)];
+    // Case 1c: hasLowerLanguageNegotiationWeight() returns FALSE.
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request));
+    // Case 1d: meetsContentEntityRoutesCondition() returns FALSE.
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request));
+
+    // Case 2: Cannot figure out the langcode.
+    $languageNegotiationContentEntityMock = $this->createPartialMock($this->getPluginClass(),
+      ['hasLowerLanguageNegotiationWeight', 'meetsContentEntityRoutesCondition', 'getLangcode']);
+    $languageNegotiationContentEntityMock->expects($this->any())
+      ->method('hasLowerLanguageNegotiationWeight')
+      ->will($this->returnValue(TRUE));
+    $languageNegotiationContentEntityMock->expects($this->any())
+      ->method('meetsContentEntityRoutesCondition')
+      ->will($this->returnValue(TRUE));
+    $languageNegotiationContentEntityMock->expects($this->exactly(2))
+      ->method('getLangcode')
+      ->willReturnOnConsecutiveCalls(
+        NULL,
+        'de'
+      );
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request));
+
+    // Case 3: Can figure out the langcode.
+    // Case 3a: via $options['language'].
+    $options['language'] = $this->languages['en'];
+    $options['query'] = NULL;
+    $bubbleableMetadataMock = $this->createMock(BubbleableMetadata::class);
+    $bubbleableMetadataMock->expects($this->exactly(3))
+      ->method('addCacheContexts')
+      ->with(['url.query_args:' . LanguageNegotiationContentEntity::QUERY_PARAMETER]);
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request, $bubbleableMetadataMock));
+    $this->assertFalse(isset($options['language']));
+    $this->assertTrue(isset($options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER]));
+    $this->assertEquals('en', $options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER]);
+
+    // Case 3a1: via $options['language'] with an additional $options['query'][static::QUERY_PARAMETER].
+    $options['language'] = $this->languages['en'];
+    $options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER] = 'xx';
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request, $bubbleableMetadataMock));
+    $this->assertFalse(isset($options['language']));
+    $this->assertEquals('xx', $options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER]);
+
+    // Case 3b: via getLangcode().
+    unset($options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER]);
+    $this->assertEquals($path, $languageNegotiationContentEntityMock->processOutbound($path, $options, $request, $bubbleableMetadataMock));
+    $this->assertEquals('de', $options['query'][LanguageNegotiationContentEntity::QUERY_PARAMETER]);
+  }
+
+  /**
+   * @covers ::getLanguageSwitchLinks
+   */
+  public function testGetLanguageSwitchLinks() {
+    $languageNegotiationContentEntity = $this->createLanguageNegotiationPlugin();
+    $languageNegotiationContentEntity->setLanguageManager($this->languageManager);
 
+    $request = Request::create('/foo', 'GET', ['param1' => 'xyz']);
+    $url = Url::fromUri('base:' . $this->randomMachineName());
+
+    $expectedLanguageSwitchLinksArray = [
+      'de' => [
+        'url' => $url,
+        'title' => $this->languages['de']->getName(),
+        'attributes' => ['class' => ['language-link']],
+        'query' => [
+          LanguageNegotiationContentEntity::QUERY_PARAMETER => 'de',
+          'param1' => 'xyz',
+        ],
+      ],
+      'en' => [
+        'url' => $url,
+        'title' => $this->languages['en']->getName(),
+        'attributes' => ['class' => ['language-link']],
+        'query' => [
+          LanguageNegotiationContentEntity::QUERY_PARAMETER => 'en',
+          'param1' => 'xyz',
+        ],
+      ],
+    ];
+    $providedLanguageSwitchLinksArray = $languageNegotiationContentEntity->getLanguageSwitchLinks($request, $this->randomMachineName(), $url);
+    $this->assertEquals(
+      $expectedLanguageSwitchLinksArray,
+      $providedLanguageSwitchLinksArray
+    );
   }
 
 }
diff --git a/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationTestBase.php b/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationTestBase.php
new file mode 100644
index 000000000000..ce2ab9b09a0b
--- /dev/null
+++ b/core/modules/language/tests/src/Unit/Plugin/LanguageNegotiation/LanguageNegotiationTestBase.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\Tests\language\Unit\Plugin\LanguageNegotiation;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Base class used for testing the various LanguageNegotiation plugins.
+ *
+ * @group language
+ */
+abstract class LanguageNegotiationTestBase extends UnitTestCase {
+
+  /**
+   * Returns the plugin class to use for creating the language negotiation plugin.
+   *
+   * @return string
+   *   The plugin class name.
+   */
+  abstract protected function getPluginClass(): string;
+
+  /**
+   * Creates a @LanguageNegotiation plugin using the factory ::create method.
+   *
+   * @return \Drupal\language\LanguageNegotiationMethodInterface
+   */
+  protected function createLanguageNegotiationPlugin(array $configuration = [], $plugin_definition = NULL) {
+    $class = $this->getPluginClass();
+    $this->assertTrue(in_array(ContainerFactoryPluginInterface::class, class_implements($class)));
+    return $class::create(\Drupal::getContainer(), $configuration, $class::METHOD_ID, $plugin_definition);
+  }
+
+}
-- 
GitLab