From d8384b58dad8bb152957d1d6fbc242a9f6930b0b Mon Sep 17 00:00:00 2001
From: phenaproxima <phenaproxima@205645.no-reply.drupal.org>
Date: Tue, 31 Aug 2021 18:06:00 +0000
Subject: [PATCH] Issue #3230510 by phenaproxima, tedbow: Turn ExclusionsTest
 into a functional test

---
 automatic_updates.services.yml                |  2 +-
 src/Event/ExcludedPathsSubscriber.php         | 44 +++++++++++-
 .../fake-site/files/private/ignore.txt        |  1 +
 .../fake-site/files/public/ignore.txt         |  1 +
 tests/fixtures/fake-site/files/staged.txt     |  1 +
 .../fake-site/sites/default/services.yml      |  2 +
 .../sites/default/settings.local.php          |  6 ++
 .../fake-site/sites/default/settings.php      |  6 ++
 .../fake-site/sites/default/staged.txt        |  1 +
 .../fake-site/sites/simpletest/ignore.txt     |  1 +
 .../AutomaticUpdatesTestServiceProvider.php   | 25 +++++++
 .../src/TestUpdater.php                       | 40 +++++++++++
 tests/src/Functional/ExclusionsTest.php       | 70 +++++++------------
 13 files changed, 151 insertions(+), 49 deletions(-)
 create mode 100644 tests/fixtures/fake-site/files/private/ignore.txt
 create mode 100644 tests/fixtures/fake-site/files/public/ignore.txt
 create mode 100644 tests/fixtures/fake-site/files/staged.txt
 create mode 100644 tests/fixtures/fake-site/sites/default/services.yml
 create mode 100644 tests/fixtures/fake-site/sites/default/settings.local.php
 create mode 100644 tests/fixtures/fake-site/sites/default/settings.php
 create mode 100644 tests/fixtures/fake-site/sites/default/staged.txt
 create mode 100644 tests/fixtures/fake-site/sites/simpletest/ignore.txt
 create mode 100644 tests/modules/automatic_updates_test/src/AutomaticUpdatesTestServiceProvider.php
 create mode 100644 tests/modules/automatic_updates_test/src/TestUpdater.php

diff --git a/automatic_updates.services.yml b/automatic_updates.services.yml
index 2b05d22d68..575eff1a4d 100644
--- a/automatic_updates.services.yml
+++ b/automatic_updates.services.yml
@@ -72,6 +72,6 @@ services:
     class: Drupal\automatic_updates\ComposerStager\ProcessFactory
   automatic_updates.excluded_paths_subscriber:
     class: Drupal\automatic_updates\Event\ExcludedPathsSubscriber
-    arguments: ['%app.root%', '%site.path%', '@file_system']
+    arguments: ['%app.root%', '%site.path%', '@file_system', '@stream_wrapper_manager']
     tags:
       - { name: event_subscriber }
diff --git a/src/Event/ExcludedPathsSubscriber.php b/src/Event/ExcludedPathsSubscriber.php
index 6665a5378d..f9d46efa45 100644
--- a/src/Event/ExcludedPathsSubscriber.php
+++ b/src/Event/ExcludedPathsSubscriber.php
@@ -4,6 +4,8 @@ namespace Drupal\automatic_updates\Event;
 
 use Drupal\automatic_updates\AutomaticUpdatesEvents;
 use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\StreamWrapper\LocalStream;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
@@ -32,6 +34,13 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface {
    */
   protected $fileSystem;
 
+  /**
+   * The stream wrapper manager service.
+   *
+   * @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   */
+  protected $streamWrapperManager;
+
   /**
    * Constructs an UpdateSubscriber.
    *
@@ -41,11 +50,14 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface {
    *   The current site path, relative to the Drupal root.
    * @param \Drupal\Core\File\FileSystemInterface $file_system
    *   The file system service.
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager
+   *   The stream wrapper manager service.
    */
-  public function __construct(string $app_root, string $site_path, FileSystemInterface $file_system) {
+  public function __construct(string $app_root, string $site_path, FileSystemInterface $file_system, StreamWrapperManagerInterface $stream_wrapper_manager) {
     $this->appRoot = $app_root;
     $this->sitePath = $site_path;
     $this->fileSystem = $file_system;
+    $this->streamWrapperManager = $stream_wrapper_manager;
   }
 
   /**
@@ -55,10 +67,13 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface {
    *   The event object.
    */
   public function preStart(PreStartEvent $event): void {
-    if ($public = $this->fileSystem->realpath('public://')) {
+    // Automated test site directories should never be staged.
+    $event->excludePath('sites/simpletest');
+
+    if ($public = $this->getFilesPath('public')) {
       $event->excludePath($public);
     }
-    if ($private = $this->fileSystem->realpath('private://')) {
+    if ($private = $this->getFilesPath('private')) {
       $event->excludePath($private);
     }
     // If this module is a git clone, exclude it.
@@ -92,6 +107,29 @@ class ExcludedPathsSubscriber implements EventSubscriberInterface {
     }
   }
 
+  /**
+   * Returns the storage path for a stream wrapper.
+   *
+   * This will only work for stream wrappers that extend
+   * \Drupal\Core\StreamWrapper\LocalStream, which includes the stream wrappers
+   * for public and private files.
+   *
+   * @param string $scheme
+   *   The stream wrapper scheme.
+   *
+   * @return string|null
+   *   The storage path for files using the given scheme, relative to the Drupal
+   *   root, or NULL if the stream wrapper does not extend
+   *   \Drupal\Core\StreamWrapper\LocalStream.
+   */
+  private function getFilesPath(string $scheme): ?string {
+    $wrapper = $this->streamWrapperManager->getViaScheme($scheme);
+    if ($wrapper instanceof LocalStream) {
+      return $wrapper->getDirectoryPath();
+    }
+    return NULL;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/tests/fixtures/fake-site/files/private/ignore.txt b/tests/fixtures/fake-site/files/private/ignore.txt
new file mode 100644
index 0000000000..4f9da38be5
--- /dev/null
+++ b/tests/fixtures/fake-site/files/private/ignore.txt
@@ -0,0 +1 @@
+This private file should never be staged.
diff --git a/tests/fixtures/fake-site/files/public/ignore.txt b/tests/fixtures/fake-site/files/public/ignore.txt
new file mode 100644
index 0000000000..ab6a7649fa
--- /dev/null
+++ b/tests/fixtures/fake-site/files/public/ignore.txt
@@ -0,0 +1 @@
+This public file should never be staged.
diff --git a/tests/fixtures/fake-site/files/staged.txt b/tests/fixtures/fake-site/files/staged.txt
new file mode 100644
index 0000000000..0087269e33
--- /dev/null
+++ b/tests/fixtures/fake-site/files/staged.txt
@@ -0,0 +1 @@
+This file should be staged.
diff --git a/tests/fixtures/fake-site/sites/default/services.yml b/tests/fixtures/fake-site/sites/default/services.yml
new file mode 100644
index 0000000000..ea9529af01
--- /dev/null
+++ b/tests/fixtures/fake-site/sites/default/services.yml
@@ -0,0 +1,2 @@
+# A fake services file that should never be staged.
+services: {}
diff --git a/tests/fixtures/fake-site/sites/default/settings.local.php b/tests/fixtures/fake-site/sites/default/settings.local.php
new file mode 100644
index 0000000000..e54016a9d2
--- /dev/null
+++ b/tests/fixtures/fake-site/sites/default/settings.local.php
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * A fake local settings file that should never be staged.
+ */
diff --git a/tests/fixtures/fake-site/sites/default/settings.php b/tests/fixtures/fake-site/sites/default/settings.php
new file mode 100644
index 0000000000..9b995731d1
--- /dev/null
+++ b/tests/fixtures/fake-site/sites/default/settings.php
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * A fake settings file that should never be staged.
+ */
diff --git a/tests/fixtures/fake-site/sites/default/staged.txt b/tests/fixtures/fake-site/sites/default/staged.txt
new file mode 100644
index 0000000000..0087269e33
--- /dev/null
+++ b/tests/fixtures/fake-site/sites/default/staged.txt
@@ -0,0 +1 @@
+This file should be staged.
diff --git a/tests/fixtures/fake-site/sites/simpletest/ignore.txt b/tests/fixtures/fake-site/sites/simpletest/ignore.txt
new file mode 100644
index 0000000000..e4525907f2
--- /dev/null
+++ b/tests/fixtures/fake-site/sites/simpletest/ignore.txt
@@ -0,0 +1 @@
+This file should not be staged.
diff --git a/tests/modules/automatic_updates_test/src/AutomaticUpdatesTestServiceProvider.php b/tests/modules/automatic_updates_test/src/AutomaticUpdatesTestServiceProvider.php
new file mode 100644
index 0000000000..b0bd0924c0
--- /dev/null
+++ b/tests/modules/automatic_updates_test/src/AutomaticUpdatesTestServiceProvider.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\automatic_updates_test;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+
+/**
+ * Modifies service definitions for testing purposes.
+ */
+class AutomaticUpdatesTestServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    parent::alter($container);
+
+    $service = 'automatic_updates.updater';
+    if ($container->hasDefinition($service)) {
+      $container->getDefinition($service)->setClass(TestUpdater::class);
+    }
+  }
+
+}
diff --git a/tests/modules/automatic_updates_test/src/TestUpdater.php b/tests/modules/automatic_updates_test/src/TestUpdater.php
new file mode 100644
index 0000000000..aed821d88a
--- /dev/null
+++ b/tests/modules/automatic_updates_test/src/TestUpdater.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\automatic_updates_test;
+
+use Drupal\automatic_updates\Updater;
+
+/**
+ * A testing updater that allows arbitrary active and stage directories.
+ */
+class TestUpdater extends Updater {
+
+  /**
+   * The active directory to use, if different from the default.
+   *
+   * @var string
+   */
+  public $activeDirectory;
+
+  /**
+   * The stage directory to use, if different from the default.
+   *
+   * @var string
+   */
+  public $stageDirectory;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getActiveDirectory(): string {
+    return $this->activeDirectory ?: parent::getActiveDirectory();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getStageDirectory(): string {
+    return $this->stageDirectory ?: parent::getStageDirectory();
+  }
+
+}
diff --git a/tests/src/Functional/ExclusionsTest.php b/tests/src/Functional/ExclusionsTest.php
index 5f3f8ede1b..0b978a5c34 100644
--- a/tests/src/Functional/ExclusionsTest.php
+++ b/tests/src/Functional/ExclusionsTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\automatic_updates\Functional;
 
-use Drupal\automatic_updates\Event\PreStartEvent;
+use Drupal\Core\Site\Settings;
 use Drupal\Tests\BrowserTestBase;
 
 /**
@@ -15,64 +15,44 @@ class ExclusionsTest extends BrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['automatic_updates'];
+  protected static $modules = ['automatic_updates_test'];
 
   /**
    * {@inheritdoc}
    */
   protected $defaultTheme = 'stark';
 
-  /**
-   * The names of site-specific settings files to mock.
-   *
-   * @var string[]
-   */
-  private const SETTINGS_FILES = [
-    'settings.php',
-    'settings.local.php',
-    'services.yml',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-
-    foreach (static::SETTINGS_FILES as $settings_file) {
-      $settings_file = "$this->siteDirectory/$settings_file";
-      touch($settings_file);
-      $this->assertFileExists($settings_file);
-    }
-  }
-
   /**
    * Tests that certain files and directories are not staged.
    *
    * @covers \Drupal\automatic_updates\Updater::getExclusions
    */
   public function testExclusions(): void {
-    $event = new PreStartEvent();
-    $this->container->get('automatic_updates.excluded_paths_subscriber')
-      ->preStart($event);
+    $stage_dir = "$this->siteDirectory/stage";
 
-    /** @var \Drupal\automatic_updates\Updater $updater */
+    /** @var \Drupal\automatic_updates_test\TestUpdater $updater */
     $updater = $this->container->get('automatic_updates.updater');
-    $reflector = new \ReflectionObject($updater);
-    $method = $reflector->getMethod('getExclusions');
-    $method->setAccessible(TRUE);
-    $exclusions = $method->invoke($updater, $event);
-
-    $this->assertContains("$this->siteDirectory/files", $exclusions);
-    $this->assertContains("$this->siteDirectory/private", $exclusions);
-    foreach (static::SETTINGS_FILES as $settings_file) {
-      $this->assertContains("$this->siteDirectory/$settings_file", $exclusions);
-    }
-    if (is_dir(__DIR__ . '/../../../.git')) {
-      $module_path = $this->container->get('extension.list.module')
-        ->getPath('automatic_updates');
-      $this->assertContains($module_path, $exclusions);
-    }
+    $updater->activeDirectory = __DIR__ . '/../../fixtures/fake-site';
+    $updater->stageDirectory = $stage_dir;
+
+    $settings = Settings::getAll();
+    $settings['file_public_path'] = 'files/public';
+    $settings['file_private_path'] = 'files/private';
+    new Settings($settings);
+
+    $updater->begin();
+    $this->assertFileDoesNotExist("$stage_dir/sites/default/settings.php");
+    $this->assertFileDoesNotExist("$stage_dir/sites/default/settings.local.php");
+    $this->assertFileDoesNotExist("$stage_dir/sites/default/services.yml");
+    // A file in sites/default, that isn't one of the site-specific settings
+    // files, should be staged.
+    $this->assertFileExists("$stage_dir/sites/default/staged.txt");
+    $this->assertDirectoryDoesNotExist("$stage_dir/sites/simpletest");
+    $this->assertDirectoryDoesNotExist("$stage_dir/files/public");
+    $this->assertDirectoryDoesNotExist("$stage_dir/files/private");
+    // A file that's in the general files directory, but not in the public or
+    // private directories, should be staged.
+    $this->assertFileExists("$stage_dir/files/staged.txt");
   }
 
 }
-- 
GitLab