StageEventsTest.php

Namespace

Drupal\Tests\package_manager\Kernel

File

core/modules/package_manager/tests/src/Kernel/StageEventsTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\Tests\package_manager\Kernel;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\package_manager\Event\PostApplyEvent;
use Drupal\package_manager\Event\PostCreateEvent;
use Drupal\package_manager\Event\PostRequireEvent;
use Drupal\package_manager\Event\PreApplyEvent;
use Drupal\package_manager\Event\PreCreateEvent;
use Drupal\package_manager\Event\PreOperationStageEvent;
use Drupal\package_manager\Event\PreRequireEvent;
use Drupal\package_manager\Event\StageEvent;
use Drupal\package_manager\Event\StatusCheckEvent;
use Drupal\package_manager\Exception\StageEventException;
use Drupal\package_manager\ValidationResult;
use PhpTuf\ComposerStager\API\Path\Value\PathListInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Tests that the stage fires events during its lifecycle.
 *
 * @covers \Drupal\package_manager\Event\StageEvent
 * @group package_manager
 * @internal
 */
class StageEventsTest extends PackageManagerKernelTestBase implements EventSubscriberInterface {
    
    /**
     * The events that were fired, in the order they were fired.
     *
     * @var string[]
     */
    private $events = [];
    
    /**
     * The stage under test.
     *
     * @var \Drupal\package_manager\StageBase
     */
    private $stage;
    
    /**
     * {@inheritdoc}
     */
    protected function setUp() : void {
        parent::setUp();
        $this->stage = $this->createStage();
    }
    
    /**
     * {@inheritdoc}
     */
    public function register(ContainerBuilder $container) : void {
        parent::register($container);
        // Since this test adds arbitrary event listeners that aren't services, we
        // need to ensure they will persist even if the container is rebuilt when
        // staged changes are applied.
        $container->getDefinition('event_dispatcher')
            ->addTag('persist');
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() : array {
        return [
            PreCreateEvent::class => 'handleEvent',
            PostCreateEvent::class => 'handleEvent',
            PreRequireEvent::class => 'handleEvent',
            PostRequireEvent::class => 'handleEvent',
            PreApplyEvent::class => 'handleEvent',
            PostApplyEvent::class => 'handleEvent',
        ];
    }
    
    /**
     * Handles a stage life cycle event.
     *
     * @param \Drupal\package_manager\Event\StageEvent $event
     *   The event object.
     */
    public function handleEvent(StageEvent $event) : void {
        $this->events[] = get_class($event);
        // The event should have a reference to the stage which fired it.
        $this->assertSame($event->stage, $this->stage);
    }
    
    /**
     * Tests that the stage fires life cycle events in a specific order.
     */
    public function testEvents() : void {
        $this->container
            ->get('event_dispatcher')
            ->addSubscriber($this);
        $this->stage
            ->create();
        $this->stage
            ->require([
            'ext-json:*',
        ]);
        $this->stage
            ->apply();
        $this->stage
            ->postApply();
        $this->stage
            ->destroy();
        $this->assertSame($this->events, [
            PreCreateEvent::class,
            PostCreateEvent::class,
            PreRequireEvent::class,
            PostRequireEvent::class,
            PreApplyEvent::class,
            PostApplyEvent::class,
        ]);
    }
    
    /**
     * Data provider for testValidationResults().
     *
     * @return string[][]
     *   The test cases.
     */
    public static function providerValidationResults() : array {
        return [
            'PreCreateEvent' => [
                PreCreateEvent::class,
            ],
            'PreRequireEvent' => [
                PreRequireEvent::class,
            ],
            'PreApplyEvent' => [
                PreApplyEvent::class,
            ],
        ];
    }
    
    /**
     * Tests that an exception is thrown if an event has validation results.
     *
     * @param string $event_class
     *   The event class to test.
     *
     * @dataProvider providerValidationResults
     */
    public function testValidationResults(string $event_class) : void {
        $error_messages = [
            t('Burn, baby, burn'),
        ];
        // Set up an event listener which will only flag an error for the event
        // class under test.
        $handler = function (StageEvent $event) use ($event_class, $error_messages) : void {
            if (get_class($event) === $event_class) {
                if ($event instanceof PreOperationStageEvent) {
                    $event->addError($error_messages);
                }
            }
        };
        $this->addEventTestListener($handler, $event_class);
        $result = ValidationResult::createError($error_messages);
        $this->assertResults([
            $result,
        ], $event_class);
    }
    
    /**
     * Tests adding validation results to events.
     */
    public function testAddResult() : void {
        $stage = $this->createStage();
        $error = ValidationResult::createError([
            t('Burn, baby, burn!'),
        ]);
        $warning = ValidationResult::createWarning([
            t('The path ahead is scary...'),
        ]);
        $excluded_paths = $this->createMock(PathListInterface::class);
        // Status check events can accept both errors and warnings.
        $event = new StatusCheckEvent($stage, $excluded_paths);
        $event->addResult($error);
        $event->addResult($warning);
        $this->assertSame([
            $error,
            $warning,
        ], $event->getResults());
        // Other stage events will accept errors, but throw an exception if you try
        // to add a warning.
        $event = new PreCreateEvent($stage, $excluded_paths);
        $event->addResult($error);
        $this->assertSame([
            $error,
        ], $event->getResults());
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('Only errors are allowed.');
        $event->addResult($warning);
    }
    
    /**
     * Tests that pre- and post-require events have access to the package lists.
     */
    public function testPackageListsAvailableToRequireEvents() : void {
        $listener = function (object $event) : void {
            $expected_runtime = [
                'drupal/core' => '9.8.2',
            ];
            $expected_dev = [
                'drupal/core-dev' => '9.8.2',
            ];
            
            /** @var \Drupal\package_manager\Event\PreRequireEvent|\Drupal\package_manager\Event\PostRequireEvent $event */
            $this->assertSame($expected_runtime, $event->getRuntimePackages());
            $this->assertSame($expected_dev, $event->getDevPackages());
        };
        $this->addEventTestListener($listener, PreRequireEvent::class);
        $this->addEventTestListener($listener, PostRequireEvent::class);
        $this->stage
            ->create();
        $this->stage
            ->require([
            'drupal/core:9.8.2',
        ], [
            'drupal/core-dev:9.8.2',
        ]);
    }
    
    /**
     * Tests exception is thrown if error is not added before stopPropagation().
     */
    public function testExceptionIfNoErrorBeforeStopPropagation() : void {
        $listener = function (PreCreateEvent $event) : void {
            $event->stopPropagation();
        };
        $this->addEventTestListener($listener, PreCreateEvent::class);
        $this->expectException(StageEventException::class);
        $this->expectExceptionMessage('Event propagation stopped without any errors added to the event. This bypasses the package_manager validation system.');
        $stage = $this->createStage();
        $stage->create();
    }

}

Classes

Title Deprecated Summary
StageEventsTest Tests that the stage fires events during its lifecycle.

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