From 18b6464316a27a9212fe7d6e28cb0c83ec3e41e1 Mon Sep 17 00:00:00 2001 From: Tim Plunkett <git@plnktt.com> Date: Wed, 22 Jan 2025 16:32:32 -0500 Subject: [PATCH] Move to level 7 --- phpstan.neon | 21 ++++++++- src/Controller/InstallerController.php | 4 ++ .../ProjectBrowserEndpointController.php | 1 + src/Element/ProjectBrowser.php | 5 +- src/Form/SettingsForm.php | 12 +++-- .../ProjectBrowserSource/DrupalCore.php | 2 +- .../DrupalDotOrgJsonApi.php | 18 ++++---- src/Plugin/ProjectBrowserSource/Recipes.php | 5 +- src/Plugin/ProjectBrowserSourceBase.php | 9 ++++ src/Plugin/ProjectBrowserSourceInterface.php | 5 ++ src/Plugin/ProjectBrowserSourceManager.php | 12 +++++ src/ProjectBrowserServiceProvider.php | 1 + src/RecipeActivator.php | 2 +- .../project_browser_test.install | 4 +- .../src/Datetime/TestTime.php | 4 +- .../src/DrupalOrgClientMiddleware.php | 14 +++--- .../ProjectBrowserTestMock.php | 8 ++-- .../Functional/EnabledSourceHandlerTest.php | 2 + .../Functional/InstallerControllerTest.php | 46 +++++++++++++------ .../ProjectBrowserInstallerUiTest.php | 14 ++++-- .../ProjectBrowserUiTest.php | 4 +- .../src/Kernel/CoreExperimentalLabelTest.php | 1 + .../Kernel/CoreNotUpdatedValidatorTest.php | 1 + tests/src/Kernel/InstallerTest.php | 8 ++-- .../PackageNotInstalledValidatorTest.php | 1 + tests/src/Kernel/RecipeActivatorTest.php | 1 + tests/src/Kernel/RecipesSourceTest.php | 10 ++-- 27 files changed, 157 insertions(+), 58 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index a95d2402b..8676e942c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 6 + level: 7 universalObjectCratesClasses: - Drupal\Core\Extension\Extension reportUnmatchedIgnoredErrors: true @@ -37,6 +37,13 @@ parameters: paths: - tests/src/Kernel/DatabaseTablesTest.php reportUnmatched: false + # Caused by \Drupal\Tests\user\Traits\UserCreationTrait::createUser() returning FALSE instead of throwing an exception. + - + message: "#^Parameter \\#1 \\$account of method Drupal\\\\Tests\\\\BrowserTestBase\\:\\:drupalLogin\\(\\) expects Drupal\\\\Core\\\\Session\\\\AccountInterface, Drupal\\\\user\\\\Entity\\\\User\\|false given\\.$#" + paths: + - tests/src/Functional + - tests/src/FunctionalJavascript + reportUnmatched: false ### Package Manager # Caused by using self instead of static as a return type in \Drupal\fixture_manipulator\FixtureManipulator. @@ -47,6 +54,18 @@ parameters: - tests/src/Kernel/CoreNotUpdatedValidatorTest.php - tests/src/Kernel/PackageNotInstalledValidatorTest.php reportUnmatched: false + # Caused by missing return type on \Drupal\Tests\package_manager\Traits\FixtureManipulatorTrait::getStageFixtureManipulator(). + - + message: "#^Call to an undefined method object\\:\\:setCorePackageVersion\\(\\)\\.$#" + paths: + - tests/src/Kernel/CoreNotUpdatedValidatorTest.php + reportUnmatched: false + # Caused by missing @throws on \Drupal\package_manager\StageBase::apply(). + - + message: "#^Dead catch \\- Drupal\\\\package_manager\\\\Exception\\\\StageEventException is never thrown in the try block\\.$#" + paths: + - tests/src/Kernel/CoreNotUpdatedValidatorTest.php + reportUnmatched: false # @todo: Remove the following rules when support is dropped for Drupal 10.2, which does not have recipes. - diff --git a/src/Controller/InstallerController.php b/src/Controller/InstallerController.php index 5b4279b98..78ba9e795 100644 --- a/src/Controller/InstallerController.php +++ b/src/Controller/InstallerController.php @@ -67,6 +67,10 @@ final class InstallerController extends ControllerBase { * {@inheritdoc} */ public static function create(ContainerInterface $container): static { + // @see \Drupal\project_browser\ProjectBrowserServiceProvider. + assert($container->get(Installer::class) instanceof Installer); + assert($container->get(InstallState::class) instanceof InstallState); + return new static( $container->get(Installer::class), $container->get(EnabledSourceHandler::class), diff --git a/src/Controller/ProjectBrowserEndpointController.php b/src/Controller/ProjectBrowserEndpointController.php index 9c07efdf9..ad6666740 100644 --- a/src/Controller/ProjectBrowserEndpointController.php +++ b/src/Controller/ProjectBrowserEndpointController.php @@ -47,6 +47,7 @@ final class ProjectBrowserEndpointController extends ControllerBase { public function getAllProjects(Request $request): JsonResponse { $id = $request->query->get('id'); if ($id) { + assert(is_string($id)); return new JsonResponse($this->enabledSource->getStoredProject($id)); } diff --git a/src/Element/ProjectBrowser.php b/src/Element/ProjectBrowser.php index eff2aa0c0..b540544a7 100644 --- a/src/Element/ProjectBrowser.php +++ b/src/Element/ProjectBrowser.php @@ -51,10 +51,13 @@ final class ProjectBrowser implements ElementInterface, ContainerFactoryPluginIn * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { + $install_readiness = $container->get(InstallReadiness::class, ContainerInterface::NULL_ON_INVALID_REFERENCE); + assert(is_null($install_readiness) || $install_readiness instanceof InstallReadiness); + return new static( $plugin_id, $plugin_definition, - $container->get(InstallReadiness::class, ContainerInterface::NULL_ON_INVALID_REFERENCE), + $install_readiness, $container->get(ModuleHandlerInterface::class), $container->get(ConfigFactoryInterface::class), ); diff --git a/src/Form/SettingsForm.php b/src/Form/SettingsForm.php index 165a6ee55..dfce29d58 100644 --- a/src/Form/SettingsForm.php +++ b/src/Form/SettingsForm.php @@ -109,6 +109,7 @@ final class SettingsForm extends ConfigFormBase { '#attributes' => [ 'id' => 'project_browser', ], + '#tabledrag' => [], ]; $options = [ 'enabled' => $this->t('Enabled'), @@ -117,6 +118,7 @@ final class SettingsForm extends ConfigFormBase { if (count($source_plugins) > 1) { $form['#attached']['library'][] = 'project_browser/tabledrag'; foreach ($options as $status => $title) { + assert(is_array($table['#tabledrag'])); $table['#tabledrag'][] = [ 'action' => 'match', 'relationship' => 'sibling', @@ -135,11 +137,11 @@ final class SettingsForm extends ConfigFormBase { 'class' => ['status-title', 'status-title-' . $status], 'no_striping' => TRUE, ], - ]; - $table['status-' . $status]['title'] = [ - '#plain_text' => $title, - '#wrapper_attributes' => [ - 'colspan' => 4, + 'title' => [ + '#plain_text' => $title, + '#wrapper_attributes' => [ + 'colspan' => 4, + ], ], ]; diff --git a/src/Plugin/ProjectBrowserSource/DrupalCore.php b/src/Plugin/ProjectBrowserSource/DrupalCore.php index 28df408e8..e08871abc 100644 --- a/src/Plugin/ProjectBrowserSource/DrupalCore.php +++ b/src/Plugin/ProjectBrowserSource/DrupalCore.php @@ -128,7 +128,7 @@ final class DrupalCore extends ProjectBrowserSourceBase { // Filter by coverage. if (!empty($query['security_advisory_coverage'])) { - $projects = array_filter($projects, fn(Project $project) => $project->isCovered); + $projects = array_filter($projects, fn(Project $project) => $project->isCovered ?? FALSE); } // Filter by categories. diff --git a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php index 48b3be9de..3b8e26f0d 100644 --- a/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php +++ b/src/Plugin/ProjectBrowserSource/DrupalDotOrgJsonApi.php @@ -159,7 +159,7 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { $response_data = Json::decode($response->getBody()->getContents()); $result['data'] = array_merge($result['data'], $response_data['data']); - if (!empty($response_data['included'])) { + if (!empty($response_data['included']) && !empty($result['included'])) { $result['included'] = array_merge($result['included'], $response_data['included']); } $iterations++; @@ -313,7 +313,7 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { $uid_info = $project['relationships']['uid']['data']; $maintenance_status = $project['relationships']['field_maintenance_status']['data'] ?? []; - if (!empty($maintenance_status)) { + if (is_array($maintenance_status)) { $maintenance_status = [ 'id' => $maintenance_status['id'], 'name' => $related[$maintenance_status['type']][$maintenance_status['id']]['name'], @@ -328,8 +328,8 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { ]; } - $module_categories = $project['relationships']['field_module_categories']['data'] ?? []; - if (!empty($module_categories)) { + $module_categories = $project['relationships']['field_module_categories']['data'] ?? NULL; + if (is_array($module_categories)) { $categories = []; foreach ($module_categories as $module_category) { $categories[] = [ @@ -340,8 +340,8 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { $module_categories = $categories; } - $project_images = $project['relationships']['field_project_images']['data'] ?? []; - if (!empty($project_images)) { + $project_images = $project['relationships']['field_project_images']['data'] ?? NULL; + if (is_array($project_images)) { $images = []; foreach ($project_images as $image) { $uri = self::DRUPAL_ORG_ENDPOINT . $related[$image['type']][$image['id']]['uri']['url']; @@ -400,8 +400,8 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { 'name' => $related[$uid_info['type']][$uid_info['id']]['name'], ], packageName: $project['attributes']['field_composer_namespace'] ?? 'drupal/' . $machine_name, - categories: $module_categories, - images: $project_images, + categories: $module_categories ?? [], + images: $project_images ?? [], url: Url::fromUri('https://www.drupal.org/project/' . $machine_name), ); $returned_list[] = $project_object; @@ -565,7 +565,7 @@ final class DrupalDotOrgJsonApi extends ProjectBrowserSourceBase { if ($extra = $version_object->getVersionExtra()) { $version = str_replace("-$extra", '', $version); } - $minor_version = $version_object->getMinorVersion() ?? 0; + $minor_version = $version_object->getMinorVersion() ?? '0'; $patch_version = explode('.', $version)[2] ?? '0'; return (int) ( diff --git a/src/Plugin/ProjectBrowserSource/Recipes.php b/src/Plugin/ProjectBrowserSource/Recipes.php index 04372d5d1..1090c70ff 100644 --- a/src/Plugin/ProjectBrowserSource/Recipes.php +++ b/src/Plugin/ProjectBrowserSource/Recipes.php @@ -43,6 +43,7 @@ final class Recipes extends ProjectBrowserSourceBase { * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { + assert(is_string($container->getParameter('app.root'))); return new static( $container->get(FileSystemInterface::class), $container->get('cache.project_browser'), @@ -89,6 +90,7 @@ final class Recipes extends ProjectBrowserSourceBase { } else { $package = file_get_contents($path . '/composer.json'); + assert(is_string($package)); $package = Json::decode($package); $package_name = $package['name']; @@ -130,7 +132,7 @@ final class Recipes extends ProjectBrowserSourceBase { // Filter by coverage. if (!empty($query['security_advisory_coverage'])) { - $projects = array_filter($projects, fn(Project $project) => $project->isCovered); + $projects = array_filter($projects, fn(Project $project) => $project->isCovered ?? FALSE); } // Filter by categories. @@ -182,6 +184,7 @@ final class Recipes extends ProjectBrowserSourceBase { if ($contrib_recipe_names) { $path = InstalledVersions::getInstallPath($contrib_recipe_names[0]); $path = $this->fileSystem->realpath($path); + assert(is_string($path)); $search_in[] = dirname($path); } diff --git a/src/Plugin/ProjectBrowserSourceBase.php b/src/Plugin/ProjectBrowserSourceBase.php index 4a29a1850..6a481821e 100644 --- a/src/Plugin/ProjectBrowserSourceBase.php +++ b/src/Plugin/ProjectBrowserSourceBase.php @@ -90,4 +90,13 @@ abstract class ProjectBrowserSourceBase extends PluginBase implements ProjectBro ); } + /** + * {@inheritdoc} + */ + public function getPluginDefinition(): array { + $definition = parent::getPluginDefinition(); + assert(is_array($definition)); + return $definition; + } + } diff --git a/src/Plugin/ProjectBrowserSourceInterface.php b/src/Plugin/ProjectBrowserSourceInterface.php index 9bb2d2703..482e153be 100644 --- a/src/Plugin/ProjectBrowserSourceInterface.php +++ b/src/Plugin/ProjectBrowserSourceInterface.php @@ -62,4 +62,9 @@ interface ProjectBrowserSourceInterface extends PluginInspectionInterface { */ public function getSortOptions(): array; + /** + * {@inheritdoc} + */ + public function getPluginDefinition(): array; + } diff --git a/src/Plugin/ProjectBrowserSourceManager.php b/src/Plugin/ProjectBrowserSourceManager.php index 7e913bb29..e7b10717f 100644 --- a/src/Plugin/ProjectBrowserSourceManager.php +++ b/src/Plugin/ProjectBrowserSourceManager.php @@ -39,4 +39,16 @@ class ProjectBrowserSourceManager extends DefaultPluginManager { $this->setCacheBackend($cache_backend, 'project_browser_source_info_plugins'); } + /** + * {@inheritdoc} + * + * @return \Drupal\project_browser\Plugin\ProjectBrowserSourceInterface + * The source plugin. + */ + public function createInstance($plugin_id, array $configuration = []) { + $instance = parent::createInstance($plugin_id, $configuration); + assert($instance instanceof ProjectBrowserSourceInterface); + return $instance; + } + } diff --git a/src/ProjectBrowserServiceProvider.php b/src/ProjectBrowserServiceProvider.php index cf8dc0b50..595a6a4d9 100644 --- a/src/ProjectBrowserServiceProvider.php +++ b/src/ProjectBrowserServiceProvider.php @@ -30,6 +30,7 @@ class ProjectBrowserServiceProvider extends ServiceProviderBase { * {@inheritdoc} */ public function alter(ContainerBuilder $container): void { + assert(is_array($container->getParameter('container.modules'))); if (array_key_exists('package_manager', $container->getParameter('container.modules'))) { parent::register($container); diff --git a/src/RecipeActivator.php b/src/RecipeActivator.php index f138b1d5d..ebbaf6ab1 100644 --- a/src/RecipeActivator.php +++ b/src/RecipeActivator.php @@ -170,7 +170,7 @@ final class RecipeActivator implements ActivatorInterface, EventSubscriberInterf } } - return $path ? $this->fileSystem->realpath($path) : NULL; + return $path ? ($this->fileSystem->realpath($path) ?: NULL) : NULL; } } diff --git a/tests/modules/project_browser_test/project_browser_test.install b/tests/modules/project_browser_test/project_browser_test.install index a44174e57..a8600740d 100644 --- a/tests/modules/project_browser_test/project_browser_test.install +++ b/tests/modules/project_browser_test/project_browser_test.install @@ -130,7 +130,9 @@ function project_browser_test_install(): void { $module_path = \Drupal::service('module_handler')->getModule('project_browser')->getPath(); $category_values = []; - $projects = Json::decode(file_get_contents($module_path . '/tests/fixtures/projects_fixture.json')); + $contents = file_get_contents($module_path . '/tests/fixtures/projects_fixture.json'); + assert(is_string($contents)); + $projects = Json::decode($contents); // Insert fixture data to the database. $query = $connection->insert('project_browser_projects')->fields([ 'nid', diff --git a/tests/modules/project_browser_test/src/Datetime/TestTime.php b/tests/modules/project_browser_test/src/Datetime/TestTime.php index 473b00f8f..b4b4100c6 100644 --- a/tests/modules/project_browser_test/src/Datetime/TestTime.php +++ b/tests/modules/project_browser_test/src/Datetime/TestTime.php @@ -46,7 +46,9 @@ class TestTime implements TimeInterface { public function getRequestTime(): int { // @phpstan-ignore-next-line if ($faked_date = \Drupal::state()->get('project_browser_test.fake_date_time')) { - return \DateTime::createFromFormat('U', $faked_date)->getTimestamp(); + if ($date_time = \DateTime::createFromFormat('U', $faked_date)) { + return $date_time->getTimestamp(); + } } return $this->decorated->getRequestTime(); } diff --git a/tests/modules/project_browser_test/src/DrupalOrgClientMiddleware.php b/tests/modules/project_browser_test/src/DrupalOrgClientMiddleware.php index 035e9575f..c76a7a580 100644 --- a/tests/modules/project_browser_test/src/DrupalOrgClientMiddleware.php +++ b/tests/modules/project_browser_test/src/DrupalOrgClientMiddleware.php @@ -128,9 +128,10 @@ class DrupalOrgClientMiddleware { $path_to_fixture = self::DRUPALORG_JSONAPI_ENDPOINT_TO_FIXTURE_MAP; if (isset($path_to_fixture[$relevant_path])) { $module_path = $this->moduleHandler->getModule('project_browser')->getPath(); - $data = file_get_contents($module_path . '/tests/fixtures/drupalorg_jsonapi/' . $path_to_fixture[$relevant_path]); - $json_response = new Response(200, [], $data); - return new FulfilledPromise($json_response); + if ($data = file_get_contents($module_path . '/tests/fixtures/drupalorg_jsonapi/' . $path_to_fixture[$relevant_path])) { + $json_response = new Response(200, [], $data); + return new FulfilledPromise($json_response); + } } throw new \Exception('Attempted call to the Drupal.org jsonapi endpoint that is not mocked in middleware: ' . $relevant_path); @@ -151,9 +152,10 @@ class DrupalOrgClientMiddleware { if (isset($path_to_fixture[$relevant_path])) { $module_path = $this->moduleHandler->getModule('project_browser')->getPath(); - $data = file_get_contents($module_path . '/tests/fixtures/drupalorg_jsonapi/' . $path_to_fixture[$relevant_path]); - $json_response = new Response(200, [], $data); - return new FulfilledPromise($json_response); + if ($data = file_get_contents($module_path . '/tests/fixtures/drupalorg_jsonapi/' . $path_to_fixture[$relevant_path])) { + $json_response = new Response(200, [], $data); + return new FulfilledPromise($json_response); + } } throw new \Exception('Attempted call to the Drupal.org endpoint that is not mocked in middleware: ' . $relevant_path); diff --git a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php index d8b0807d9..3b8b8273d 100644 --- a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php +++ b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php @@ -288,7 +288,9 @@ final class ProjectBrowserTestMock extends ProjectBrowserSourceBase { */ protected function getCategoryData(): array { $module_path = $this->moduleHandler->getModule('project_browser')->getPath(); - $category_list = Json::decode(file_get_contents($module_path . '/tests/fixtures/category_list.json')) ?? []; + $contents = file_get_contents($module_path . '/tests/fixtures/category_list.json'); + assert(is_string($contents)); + $category_list = Json::decode($contents) ?? []; $categories = []; foreach ($category_list as $category) { $categories[$category['tid']] = [ @@ -363,7 +365,7 @@ final class ProjectBrowserTestMock extends ProjectBrowserSourceBase { $categories = $this->getCategoryData(); $returned_list = []; - if ($api_response) { + if (is_array($api_response)) { foreach ($api_response['list'] as $project_data) { $avatar_url = 'https://git.drupalcode.org/project/' . $project_data['field_project_machine_name'] . '/-/avatar'; $logo = [ @@ -381,7 +383,7 @@ final class ProjectBrowserTestMock extends ProjectBrowserSourceBase { isCompatible: TRUE, isMaintained: in_array($project_data['maintenance_status'], self::MAINTAINED_VALUES), isCovered: in_array($project_data['field_security_advisory_coverage'], self::COVERED_VALUES), - projectUsageTotal: array_reduce($project_data['project_data']['project_usage'] ?? [], fn($total, $project_usage) => $total + $project_usage) ?: 0, + projectUsageTotal: (int) array_reduce($project_data['project_data']['project_usage'] ?? [], fn($total, $project_usage) => $total + $project_usage) ?: 0, machineName: $project_data['field_project_machine_name'], body: $this->relativeToAbsoluteUrls($project_data['project_data']['body'], 'https://www.drupal.org'), title: $project_data['title'], diff --git a/tests/src/Functional/EnabledSourceHandlerTest.php b/tests/src/Functional/EnabledSourceHandlerTest.php index dd18731db..9f9a05ca1 100644 --- a/tests/src/Functional/EnabledSourceHandlerTest.php +++ b/tests/src/Functional/EnabledSourceHandlerTest.php @@ -54,6 +54,7 @@ class EnabledSourceHandlerTest extends BrowserTestBase { $handler = $this->container->get(EnabledSourceHandler::class); $projects = $handler->getProjects('project_browser_test_mock'); + assert(!empty($projects)); $list = reset($projects)->list; $this->assertNotEmpty($list); $project = reset($list); @@ -74,6 +75,7 @@ class EnabledSourceHandlerTest extends BrowserTestBase { // and commands set. $projects = $this->container->get(EnabledSourceHandler::class) ->getProjects('project_browser_test_mock'); + assert(!empty($projects)); $list = reset($projects)->list; $this->assertNotEmpty($list); $project = reset($list); diff --git a/tests/src/Functional/InstallerControllerTest.php b/tests/src/Functional/InstallerControllerTest.php index 4b584a872..1bb7a0d50 100644 --- a/tests/src/Functional/InstallerControllerTest.php +++ b/tests/src/Functional/InstallerControllerTest.php @@ -164,7 +164,9 @@ class InstallerControllerTest extends BrowserTestBase { ]); $query->execute(); $this->initPackageManager(); - $this->installer = $this->container->get(Installer::class); + /** @var \Drupal\project_browser\ComposerInstaller\Installer $installer */ + $installer = $this->container->get(Installer::class); + $this->installer = $installer; $this->drupalLogin($this->drupalCreateUser(['administer modules'])); $this->config('project_browser.admin_settings') ->set('enabled_sources', ['project_browser_test_mock', 'drupal_core']) @@ -193,7 +195,7 @@ class InstallerControllerTest extends BrowserTestBase { * @covers ::begin */ public function testInstallSecurityRevokedModule(): void { - $this->assertSame([], $this->container->get(InstallState::class)->toArray()); + $this->assertSame([], $this->getInstallState()->toArray()); $contents = $this->drupalGet('admin/modules/project_browser/install-begin'); $this->stageId = Json::decode($contents)['stage_id']; $response = $this->getPostResponse('project_browser.stage.require', 'project_browser_test_mock/security_revoked_module', [ @@ -209,7 +211,7 @@ class InstallerControllerTest extends BrowserTestBase { * @covers ::require */ public function testInstallAlreadyPresentPackage(): void { - $this->assertSame([], $this->container->get(InstallState::class)->toArray()); + $this->assertSame([], $this->getInstallState()->toArray()); // Though core is not available as a choice in project browser, it works // well for the purposes of this test as it's definitely already added // via composer. @@ -228,7 +230,7 @@ class InstallerControllerTest extends BrowserTestBase { * @covers ::begin */ private function doStart(): void { - $this->assertSame([], $this->container->get(InstallState::class)->toArray()); + $this->assertSame([], $this->getInstallState()->toArray()); $contents = $this->drupalGet('admin/modules/project_browser/install-begin'); $this->stageId = Json::decode($contents)['stage_id']; $this->assertSession()->statusCodeEquals(200); @@ -450,10 +452,14 @@ class InstallerControllerTest extends BrowserTestBase { $this->assertSame($expected_message, $response['message']); if ($response['unlock_url']) { - $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', parse_url($response['unlock_url'], PHP_URL_PATH)); - $query = parse_url($response['unlock_url'], PHP_URL_QUERY); - parse_str($query, $query); + $path_string = parse_url($response['unlock_url'], PHP_URL_PATH); + $this->assertIsString($path_string); + $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', $path_string); + $query_string = parse_url($response['unlock_url'], PHP_URL_QUERY); + $this->assertIsString($query_string); + parse_str($query_string, $query); $this->assertNotEmpty($query['token']); + $this->assertIsString($query['destination']); $this->assertStringEndsWith('/admin/modules/browse/project_browser_test_mock', $query['destination']); } }; @@ -468,7 +474,7 @@ class InstallerControllerTest extends BrowserTestBase { 'status' => 'requiring', ], ]; - $this->assertSame($expected, $this->container->get(InstallState::class)->toArray()); + $this->assertSame($expected, $this->getInstallState()->toArray()); $this->assertFalse($this->installer->isAvailable()); $this->assertFalse($this->installer->isApplying()); TestTime::setFakeTimeByOffset("+800 seconds"); @@ -517,8 +523,8 @@ class InstallerControllerTest extends BrowserTestBase { $json = Json::decode($content); $this->assertSame('The process for adding projects is locked, but that lock has expired. Use [+ unlock link] to unlock the process and try to add the project again.', $json['message']); $unlock_url = parse_url($json['unlock_url']); - parse_str($unlock_url['query'], $unlock_url['query']); - $unlock_content = $this->drupalGet($unlock_url['path'], ['query' => $unlock_url['query']]); + parse_str($unlock_url['query'] ?? '', $unlock_url['query']); + $unlock_content = $this->drupalGet($unlock_url['path'] ?? '', ['query' => $unlock_url['query']]); $this->assertSession()->statusCodeEquals(200); $this->assertTrue($this->installer->isAvailable()); $this->assertStringContainsString('Operation complete, you can add a new project again.', $unlock_content); @@ -533,7 +539,7 @@ class InstallerControllerTest extends BrowserTestBase { */ public function testCanBreakStageWithMissingProjectBrowserLock(): void { $this->doStart(); - $this->container->get(InstallState::class)->deleteAll(); + $this->getInstallState()->deleteAll(); $content = $this->drupalGet('admin/modules/project_browser/install-begin', [ 'query' => ['source' => 'project_browser_test_mock'], ]); @@ -543,8 +549,8 @@ class InstallerControllerTest extends BrowserTestBase { $json = Json::decode($content); $this->assertSame('The process for adding projects is locked, but that lock has expired. Use [+ unlock link] to unlock the process and try to add the project again.', $json['message']); $unlock_url = parse_url($json['unlock_url']); - parse_str($unlock_url['query'], $unlock_url['query']); - $unlock_content = $this->drupalGet($unlock_url['path'], ['query' => $unlock_url['query']]); + parse_str($unlock_url['query'] ?? '', $unlock_url['query']); + $unlock_content = $this->drupalGet($unlock_url['path'] ?? '', ['query' => $unlock_url['query']]); $this->assertSession()->statusCodeEquals(200); $this->assertTrue($this->installer->isAvailable()); $this->assertStringContainsString('Operation complete, you can add a new project again.', $unlock_content); @@ -592,7 +598,7 @@ class InstallerControllerTest extends BrowserTestBase { 'source' => $source, 'status' => $status, ]; - $this->assertSame($expect_install, $this->container->get(InstallState::class)->toArray()); + $this->assertSame($expect_install, $this->getInstallState()->toArray()); $this->drupalGet("/admin/modules/project_browser/install_in_progress/$project_id"); $this->assertSame(sprintf('{"status":1,"phase":"%s"}', $status), $this->getSession()->getPage()->getContent()); $this->drupalGet('/admin/modules/project_browser/install_in_progress/project_browser_test_mock/metatag'); @@ -626,4 +632,16 @@ class InstallerControllerTest extends BrowserTestBase { return $this->makeApiRequest('POST', $post_url, $request_options); } + /** + * Gets the install state. + * + * @return \Drupal\project_browser\InstallState + * The install state. + */ + private function getInstallState(): InstallState { + $install_state = $this->container->get(InstallState::class); + assert($install_state instanceof InstallState); + return $install_state; + } + } diff --git a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php index 1bfe7eac7..71b145438 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserInstallerUiTest.php @@ -55,7 +55,9 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->initPackageManager(); - $this->installState = $this->container->get(InstallState::class); + /** @var \Drupal\project_browser\InstallState $install_state */ + $install_state = $this->container->get(InstallState::class); + $this->installState = $install_state; $this->config('project_browser.admin_settings') ->set('enabled_sources', ['project_browser_test_mock']) @@ -451,10 +453,14 @@ class ProjectBrowserInstallerUiTest extends WebDriverTestBase { $this->assertSame('Install Cream cheese on a bagel', $download_button->getText()); $download_button->click(); $unlock_url = $assert_session->waitForElementVisible('css', "#unlock-link")->getAttribute('href'); - $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', parse_url($unlock_url, PHP_URL_PATH)); - $query = parse_url($unlock_url, PHP_URL_QUERY); - parse_str($query, $query); + $path_string = parse_url($unlock_url, PHP_URL_PATH); + $this->assertIsString($path_string); + $this->assertStringEndsWith('/admin/modules/project_browser/install/unlock', $path_string); + $query_string = parse_url($unlock_url, PHP_URL_QUERY); + $this->assertIsString($query_string); + parse_str($query_string, $query); $this->assertNotEmpty($query['token']); + $this->assertIsString($query['destination']); $this->assertStringEndsWith('/admin/modules/browse/project_browser_test_mock', $query['destination']); } diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php index eb869dd1f..d65af3c06 100644 --- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php +++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php @@ -214,7 +214,9 @@ class ProjectBrowserUiTest extends WebDriverTestBase { // The first textarea should have the command to require the module. $this->assertSame('composer require drupal/helvetica', $command_boxes[0]->getValue()); // And the second textarea should have the command to install it. - $this->assertStringEndsWith('drush install helvetica', $command_boxes[1]->getValue()); + $value = $command_boxes[1]->getValue(); + $this->assertIsString($value); + $this->assertStringEndsWith('drush install helvetica', $value); // Tests alt text for copy command image. $download_commands = $page->findAll('css', '.command-box img'); diff --git a/tests/src/Kernel/CoreExperimentalLabelTest.php b/tests/src/Kernel/CoreExperimentalLabelTest.php index 36a2249a1..05725211e 100644 --- a/tests/src/Kernel/CoreExperimentalLabelTest.php +++ b/tests/src/Kernel/CoreExperimentalLabelTest.php @@ -34,6 +34,7 @@ class CoreExperimentalLabelTest extends KernelTestBase { * @covers ::getProjectData */ public function testCoreExperimentalLabel(): void { + /** @var \Drupal\project_browser\Plugin\ProjectBrowserSourceInterface $plugin_instance */ $plugin_instance = $this->container->get(ProjectBrowserSourceManager::class) ->createInstance('drupal_core'); $modules_to_test = ['Experimental Test', 'System']; diff --git a/tests/src/Kernel/CoreNotUpdatedValidatorTest.php b/tests/src/Kernel/CoreNotUpdatedValidatorTest.php index ef1599beb..ee83bd591 100644 --- a/tests/src/Kernel/CoreNotUpdatedValidatorTest.php +++ b/tests/src/Kernel/CoreNotUpdatedValidatorTest.php @@ -75,6 +75,7 @@ class CoreNotUpdatedValidatorTest extends PackageManagerKernelTestBase { if ($core_updated) { $this->getStageFixtureManipulator()->setCorePackageVersion('9.8.1'); } + /** @var \Drupal\project_browser\ComposerInstaller\Installer $installer */ $installer = $this->container->get(Installer::class); $installer->create(); $installer->require(['org/package-name']); diff --git a/tests/src/Kernel/InstallerTest.php b/tests/src/Kernel/InstallerTest.php index e595444fe..04cc7ab64 100644 --- a/tests/src/Kernel/InstallerTest.php +++ b/tests/src/Kernel/InstallerTest.php @@ -79,14 +79,15 @@ class InstallerTest extends PackageManagerKernelTestBase { /** * Tests exception handling during calls to Composer Stager commit. * - * @param string $thrown_class + * @param class-string<\Throwable> $thrown_class * The throwable class that should be thrown by Composer Stager. - * @param string|null $expected_class + * @param class-string<\Throwable> $expected_class * The expected exception class. * * @dataProvider providerCommitException */ - public function testCommitException(string $thrown_class, ?string $expected_class = NULL): void { + public function testCommitException(string $thrown_class, string $expected_class): void { + /** @var \Drupal\project_browser\ComposerInstaller\Installer $installer */ $installer = $this->container->get(Installer::class); $installer->create(); $installer->require(['org/package-name']); @@ -114,6 +115,7 @@ class InstallerTest extends PackageManagerKernelTestBase { * @covers ::dispatch */ public function testInstallException(): void { + /** @var \Drupal\project_browser\ComposerInstaller\Installer $installer */ $installer = $this->container->get(Installer::class); $installer->create(); $installer->require(['org/package-name']); diff --git a/tests/src/Kernel/PackageNotInstalledValidatorTest.php b/tests/src/Kernel/PackageNotInstalledValidatorTest.php index 8a8bd2cc0..eff4ae007 100644 --- a/tests/src/Kernel/PackageNotInstalledValidatorTest.php +++ b/tests/src/Kernel/PackageNotInstalledValidatorTest.php @@ -88,6 +88,7 @@ class PackageNotInstalledValidatorTest extends PackageManagerKernelTestBase { // entry for it, so we can 'composer require' it later. ->removePackage('drupal/new_module') ->commitChanges(); + /** @var \Drupal\project_browser\ComposerInstaller\Installer $installer */ $installer = $this->container->get(Installer::class); try { $installer->create(); diff --git a/tests/src/Kernel/RecipeActivatorTest.php b/tests/src/Kernel/RecipeActivatorTest.php index c832f0d1c..c25952a2e 100644 --- a/tests/src/Kernel/RecipeActivatorTest.php +++ b/tests/src/Kernel/RecipeActivatorTest.php @@ -56,6 +56,7 @@ class RecipeActivatorTest extends KernelTestBase { packageName: 'My Project', type: ProjectType::Recipe, ); + /** @var \Drupal\project_browser\ActivatorInterface $activator */ $activator = $this->container->get(ActivatorInterface::class); // As this project is not installed the RecipeActivator::getPath() will // return NULL in RecipeActivator::getStatus() and it will return the diff --git a/tests/src/Kernel/RecipesSourceTest.php b/tests/src/Kernel/RecipesSourceTest.php index f6fa34f07..32f993ffd 100644 --- a/tests/src/Kernel/RecipesSourceTest.php +++ b/tests/src/Kernel/RecipesSourceTest.php @@ -68,8 +68,7 @@ class RecipesSourceTest extends KernelTestBase { $this->setSetting('extension_discovery_scan_tests', TRUE); /** @var \Drupal\project_browser\Plugin\ProjectBrowserSourceInterface $source */ - $source = $this->container->get(ProjectBrowserSourceManager::class) - ->createInstance('recipes'); + $source = $this->container->get(ProjectBrowserSourceManager::class)->createInstance('recipes'); // Generate a fake recipe in the temporary directory. $generated_recipe_name = uniqid(); @@ -149,10 +148,9 @@ class RecipesSourceTest extends KernelTestBase { ]) ->save(); - /** @var \Drupal\project_browser\ProjectBrowser\ProjectsResultsPage $projects */ - $projects = $this->container->get(ProjectBrowserSourceManager::class) - ->createInstance('recipes') - ->getProjects(); + /** @var \Drupal\project_browser\Plugin\ProjectBrowserSourceInterface $source */ + $source = $this->container->get(ProjectBrowserSourceManager::class)->createInstance('recipes'); + $projects = $source->getProjects(); $found_recipe_names = array_column($projects->list, 'machineName'); // The `example` recipe (from core) should always be hidden, even if it's in -- GitLab