class UpdateFetcherTest

Same name and namespace in other branches
  1. 9 core/modules/update/tests/src/Unit/UpdateFetcherTest.php \Drupal\Tests\update\Unit\UpdateFetcherTest
  2. 8.9.x core/modules/update/tests/src/Unit/UpdateFetcherTest.php \Drupal\Tests\update\Unit\UpdateFetcherTest
  3. 11.x core/modules/update/tests/src/Unit/UpdateFetcherTest.php \Drupal\Tests\update\Unit\UpdateFetcherTest

Tests update functionality unrelated to the database.

@coversDefaultClass \Drupal\update\UpdateFetcher

@group update

Hierarchy

Expanded class hierarchy of UpdateFetcherTest

File

core/modules/update/tests/src/Unit/UpdateFetcherTest.php, line 25

Namespace

Drupal\Tests\update\Unit
View source
class UpdateFetcherTest extends UnitTestCase {
  
  /**
   * The update fetcher to use.
   *
   * @var \Drupal\update\UpdateFetcher
   */
  protected $updateFetcher;
  
  /**
   * History of requests/responses.
   *
   * @var array
   */
  protected $history = [];
  
  /**
   * Mock HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $mockHttpClient;
  
  /**
   * Mock config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $mockConfigFactory;
  
  /**
   * A test project to fetch with.
   *
   * @var array
   */
  protected $testProject;
  
  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected LoggerInterface $logger;
  
  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $this->mockConfigFactory = $this->getConfigFactoryStub([
      'update.settings' => [
        'fetch_url' => 'http://www.example.com',
      ],
    ]);
    $this->mockHttpClient = $this->createMock('\\GuzzleHttp\\ClientInterface');
    $settings = new Settings([]);
    $this->logger = new TestLogger();
    $this->updateFetcher = new UpdateFetcher($this->mockConfigFactory, $this->mockHttpClient, $settings, $this->logger);
    $this->testProject = [
      'name' => 'update_test',
      'project_type' => '',
      'info' => [
        'version' => '',
        'project status url' => 'https://www.example.com',
      ],
      'includes' => [
        'module1' => 'Module 1',
        'module2' => 'Module 2',
      ],
    ];
  }
  
  /**
   * Tests that buildFetchUrl() builds the URL correctly.
   *
   * @param array $project
   *   A keyed array of project information matching results from
   *   \Drupal\update\UpdateManager::getProjects().
   * @param string $site_key
   *   A string to mimic an anonymous site key hash.
   * @param string $expected
   *   The expected URL returned from UpdateFetcher::buildFetchUrl()
   *
   * @dataProvider providerTestUpdateBuildFetchUrl
   *
   * @see \Drupal\update\UpdateFetcher::buildFetchUrl()
   */
  public function testUpdateBuildFetchUrl(array $project, $site_key, $expected) : void {
    $url = $this->updateFetcher
      ->buildFetchUrl($project, $site_key);
    $this->assertEquals($url, $expected);
    $this->assertFalse($this->logger
      ->hasErrorRecords());
  }
  
  /**
   * Provide test data for self::testUpdateBuildFetchUrl().
   *
   * @return array
   *   An array of arrays, each containing:
   *   - 'project' - An array matching a project's .info file structure.
   *   - 'site_key' - An arbitrary site key.
   *   - 'expected' - The expected URL from UpdateFetcher::buildFetchUrl().
   */
  public static function providerTestUpdateBuildFetchUrl() {
    $data = [];
    // First test that we didn't break the trivial case.
    $project['name'] = 'update_test';
    $project['project_type'] = '';
    $project['info']['version'] = '';
    $project['info']['project status url'] = 'http://www.example.com';
    $project['includes'] = [
      'module1' => 'Module 1',
      'module2' => 'Module 2',
    ];
    $site_key = '';
    $expected = "http://www.example.com/{$project['name']}/current";
    $data[] = [
      $project,
      $site_key,
      $expected,
    ];
    // For uninstalled projects it shouldn't add the site key either.
    $site_key = 'site_key';
    $project['project_type'] = 'disabled';
    $expected = "http://www.example.com/{$project['name']}/current";
    $data[] = [
      $project,
      $site_key,
      $expected,
    ];
    // For installed projects, test adding the site key.
    $project['project_type'] = '';
    $expected = "http://www.example.com/{$project['name']}/current";
    $expected .= '?site_key=site_key';
    $expected .= '&list=' . rawurlencode('module1,module2');
    $data[] = [
      $project,
      $site_key,
      $expected,
    ];
    // Test when the URL contains a question mark.
    $project['info']['project status url'] = 'http://www.example.com/?project=';
    $expected = "http://www.example.com/?project=/{$project['name']}/current";
    $expected .= '&site_key=site_key';
    $expected .= '&list=' . rawurlencode('module1,module2');
    $data[] = [
      $project,
      $site_key,
      $expected,
    ];
    return $data;
  }
  
  /**
   * Mocks the HTTP client.
   *
   * @param \GuzzleHttp\Psr7\Response ...
   *   Variable number of Response objects that the mocked client should return.
   */
  protected function mockClient(Response ...$responses) {
    // Create a mock and queue responses.
    $mock_handler = new MockHandler($responses);
    $handler_stack = HandlerStack::create($mock_handler);
    $history = Middleware::history($this->history);
    $handler_stack->push($history);
    $this->mockHttpClient = new Client([
      'handler' => $handler_stack,
    ]);
  }
  
  /**
   * @covers ::doRequest
   * @covers ::fetchProjectData
   */
  public function testUpdateFetcherNoFallback() : void {
    // First, try without the HTTP fallback setting, and HTTPS mocked to fail.
    $settings = new Settings([]);
    $this->mockClient(new Response(500, [], 'HTTPS failed'));
    $update_fetcher = new UpdateFetcher($this->mockConfigFactory, $this->mockHttpClient, $settings, $this->logger);
    $data = $update_fetcher->fetchProjectData($this->testProject, '');
    // There should only be one request / response pair.
    $this->assertCount(1, $this->history);
    $request = $this->history[0]['request'];
    $this->assertNotEmpty($request);
    // It should have only been an HTTPS request.
    $this->assertEquals('https', $request->getUri()
      ->getScheme());
    // And it should have failed.
    $response = $this->history[0]['response'];
    $this->assertEquals(500, $response->getStatusCode());
    $this->assertEmpty($data);
    $this->assertTrue($this->logger
      ->hasErrorThatPasses(function (array $record) {
      return $record['context']['@message'] === "Server error: `GET https://www.example.com/update_test/current` resulted in a `500 Internal Server Error` response:\nHTTPS failed\n";
    }));
  }
  
  /**
   * @covers ::doRequest
   * @covers ::fetchProjectData
   */
  public function testUpdateFetcherHttpFallback() : void {
    $settings = new Settings([
      'update_fetch_with_http_fallback' => TRUE,
    ]);
    $this->mockClient(new Response(500, [], 'HTTPS failed'), new Response(200, [], 'HTTP worked'));
    $update_fetcher = new UpdateFetcher($this->mockConfigFactory, $this->mockHttpClient, $settings, $this->logger);
    $data = $update_fetcher->fetchProjectData($this->testProject, '');
    // There should be two request / response pairs.
    $this->assertCount(2, $this->history);
    // The first should have been HTTPS and should have failed.
    $first_try = $this->history[0];
    $this->assertNotEmpty($first_try);
    $this->assertEquals('https', $first_try['request']->getUri()
      ->getScheme());
    $this->assertEquals(500, $first_try['response']->getStatusCode());
    // The second should have been the HTTP fallback and should have worked.
    $second_try = $this->history[1];
    $this->assertNotEmpty($second_try);
    $this->assertEquals('http', $second_try['request']->getUri()
      ->getScheme());
    $this->assertEquals(200, $second_try['response']->getStatusCode());
    // Although this is a bogus mocked response, it's what fetchProjectData()
    // should return in this case.
    $this->assertEquals('HTTP worked', $data);
    $this->assertTrue($this->logger
      ->hasErrorThatPasses(function (array $record) {
      return $record['context']['@message'] === "Server error: `GET https://www.example.com/update_test/current` resulted in a `500 Internal Server Error` response:\nHTTPS failed\n";
    }));
  }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
RandomGeneratorTrait::getRandomGenerator protected function Gets the random generator for the utility methods.
RandomGeneratorTrait::randomMachineName protected function Generates a unique random string containing letters and numbers.
RandomGeneratorTrait::randomObject public function Generates a random PHP object.
RandomGeneratorTrait::randomString public function Generates a pseudo-random string of ASCII characters of codes 32 to 126.
RandomGeneratorTrait::randomStringValidate Deprecated public function Callback for random string validation.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::setUpBeforeClass public static function
UnitTestCase::__get public function
UpdateFetcherTest::$history protected property History of requests/responses.
UpdateFetcherTest::$logger protected property The logger.
UpdateFetcherTest::$mockConfigFactory protected property Mock config factory.
UpdateFetcherTest::$mockHttpClient protected property Mock HTTP client.
UpdateFetcherTest::$testProject protected property A test project to fetch with.
UpdateFetcherTest::$updateFetcher protected property The update fetcher to use.
UpdateFetcherTest::mockClient protected function Mocks the HTTP client.
UpdateFetcherTest::providerTestUpdateBuildFetchUrl public static function Provide test data for self::testUpdateBuildFetchUrl().
UpdateFetcherTest::setUp protected function Overrides UnitTestCase::setUp
UpdateFetcherTest::testUpdateBuildFetchUrl public function Tests that buildFetchUrl() builds the URL correctly.
UpdateFetcherTest::testUpdateFetcherHttpFallback public function @covers ::doRequest[[api-linebreak]]
@covers ::fetchProjectData[[api-linebreak]]
UpdateFetcherTest::testUpdateFetcherNoFallback public function @covers ::doRequest[[api-linebreak]]
@covers ::fetchProjectData[[api-linebreak]]

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.