-
-
Notifications
You must be signed in to change notification settings - Fork 14
[#571] Added ConfigOverrideTrait to disable Drupal config overrides via tags. #626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
c81c884
[#571] Added ConfigOverrideTrait to disable Drupal config overrides v…
AlexSkrypnyk 60e6cc4
[#571] Added end-to-end fixture proving settings.php overrides are by…
AlexSkrypnyk 5ce23f4
Addressed code review: prevented state bleed and added feature tag.
AlexSkrypnyk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace DrevOps\BehatSteps\Drupal; | ||
|
|
||
| use Behat\Behat\Hook\Scope\BeforeScenarioScope; | ||
| use Behat\Behat\Hook\Scope\BeforeStepScope; | ||
| use Behat\Hook\BeforeScenario; | ||
| use Behat\Hook\BeforeStep; | ||
| use Behat\Mink\Driver\Selenium2Driver; | ||
|
|
||
| /** | ||
| * Disable Drupal config overrides from settings.php during a scenario. | ||
| * | ||
| * Config overrides set in `settings.php` replace the stored configuration at | ||
| * runtime. They cannot be disabled from the Behat process because tests run | ||
| * in a separate process from the system under test (SUT). | ||
| * | ||
| * This trait signals the SUT - through a request header, a `$_SERVER` entry | ||
| * and an environment variable - that specific config objects should be read | ||
| * from their original (unoverridden) values. The SUT is responsible for | ||
| * reading that signal and calling `ImmutableConfig::getOriginal()` instead of | ||
| * `ImmutableConfig::get()` for the listed config names. | ||
| * | ||
| * Activated by adding `@disable-config-override:CONFIG_NAME` tags to a | ||
| * feature or scenario. Multiple tags are combined into a comma-separated | ||
| * list. Runs on every step because some steps reset headers set earlier in | ||
| * the scenario. | ||
| * | ||
| * Limitations: | ||
| * - Cannot be used with Selenium/JavaScript drivers (the underlying driver | ||
| * does not expose request headers). | ||
| * - The SUT must implement support for the `X-Config-No-Override` header, | ||
| * the `HTTP_X_CONFIG_NO_OVERRIDE` `$_SERVER` entry or the matching | ||
| * environment variable. An example implementation: | ||
| * @code | ||
| * public function getConfigValue(string $name, string $key): mixed { | ||
| * $config = $this->configFactory->get($name); | ||
| * $header = $_SERVER['HTTP_X_CONFIG_NO_OVERRIDE'] ?? getenv('HTTP_X_CONFIG_NO_OVERRIDE') ?: ''; | ||
| * if (in_array($name, array_map('trim', explode(',', $header)), TRUE)) { | ||
| * return $config->getOriginal($key, FALSE); | ||
| * } | ||
| * return $config->get($key); | ||
| * } | ||
| * @endcode | ||
| * | ||
| * Soft dependency: if the consuming context also uses `RestTrait`, the | ||
| * `$restHeaders` array is updated so standalone REST requests receive the | ||
| * same signal. | ||
| * | ||
| * Example: | ||
| * @code | ||
| * @api @disable-config-override:system.site @disable-config-override:myconfig.settings | ||
| * Scenario: Render the page with original config values | ||
| * When I visit "/" | ||
| * Then the response should contain "Original site name" | ||
| * @endcode | ||
| * | ||
| * Skip processing with tags: `@behat-steps-skip:configOverrideBeforeScenario` | ||
| * and `@behat-steps-skip:configOverrideBeforeStep`. | ||
| */ | ||
| trait ConfigOverrideTrait { | ||
|
|
||
| /** | ||
| * Config names parsed from `@disable-config-override:*` tags. | ||
| * | ||
| * @var array<int, string> | ||
| */ | ||
| protected array $configOverrideDisabledNames = []; | ||
|
|
||
| /** | ||
| * Whether the `BeforeStep` hook should be skipped for this scenario. | ||
| */ | ||
| protected bool $configOverrideSkipBeforeStep = FALSE; | ||
|
|
||
| /** | ||
| * Collect `@disable-config-override:*` tags for the current scenario. | ||
| */ | ||
| #[BeforeScenario] | ||
| public function configOverrideBeforeScenario(BeforeScenarioScope $scope): void { | ||
| $this->configOverrideDisabledNames = []; | ||
| $this->configOverrideSkipBeforeStep = FALSE; | ||
|
|
||
| if ($scope->getScenario()->hasTag('behat-steps-skip:' . __FUNCTION__)) { | ||
| return; | ||
| } | ||
|
|
||
| // BeforeStep scope does not have access to scenario tags, so resolve the | ||
| // skip flag here. | ||
| if ($scope->getScenario()->hasTag('behat-steps-skip:configOverrideBeforeStep')) { | ||
| $this->configOverrideSkipBeforeStep = TRUE; | ||
| } | ||
|
|
||
| $tags = array_unique(array_merge($scope->getFeature()->getTags(), $scope->getScenario()->getTags())); | ||
| $prefix = 'disable-config-override:'; | ||
| foreach ($tags as $tag) { | ||
| if (str_starts_with($tag, $prefix)) { | ||
| $name = substr($tag, strlen($prefix)); | ||
| if ($name !== '' && !in_array($name, $this->configOverrideDisabledNames, TRUE)) { | ||
| $this->configOverrideDisabledNames[] = $name; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Apply the `X-Config-No-Override` signal before every step. | ||
| * | ||
| * This runs on every step because some steps reset headers set earlier in | ||
| * the scenario (for example, Drupal Extension login steps). | ||
| */ | ||
| #[BeforeStep] | ||
| public function configOverrideBeforeStep(BeforeStepScope $scope): void { | ||
| if ($this->configOverrideSkipBeforeStep || $this->configOverrideDisabledNames === []) { | ||
| return; | ||
| } | ||
|
|
||
| $value = implode(',', $this->configOverrideDisabledNames); | ||
|
|
||
| // Set request header on the Mink driver for BrowserKit-based sessions. | ||
| // Selenium-based drivers cannot set request headers - skip silently. | ||
| $driver = $this->getSession()->getDriver(); | ||
| if (!$driver instanceof Selenium2Driver) { | ||
| $driver->setRequestHeader('X-Config-No-Override', $value); | ||
| } | ||
|
|
||
| // Soft dependency on RestTrait: propagate to the standalone REST client | ||
| // when RestTrait is also used by the consuming context. | ||
| // @phpstan-ignore-next-line function.alreadyNarrowedType | ||
| if (property_exists($this, 'restHeaders')) { | ||
| $this->restHeaders['X-Config-No-Override'] = $value; | ||
| } | ||
|
|
||
| // For SUTs accessed via direct code invocation within the same process. | ||
| $_SERVER['HTTP_X_CONFIG_NO_OVERRIDE'] = $value; | ||
|
|
||
| // For SUTs accessed via Drush subprocesses. | ||
| putenv('HTTP_X_CONFIG_NO_OVERRIDE=' . $value); | ||
| } | ||
|
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| Feature: Check that ConfigOverrideTrait works | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| As Behat Steps library developer | ||
| I want to provide a way to disable Drupal config overrides for a scenario | ||
| So that users can test original config values without redeploying the SUT | ||
|
|
||
| # The fixture site has the following overrides in settings.php: | ||
| # $config['system.site']['name'] = 'Overridden Site Name'; | ||
| # $config['system.site']['slogan'] = 'Overridden Slogan'; | ||
| # The stored (original) name is 'Drush Site-Install'. | ||
|
|
||
| @api | ||
| Scenario: Without the tag, the SUT serves the settings.php-overridden config value | ||
| When I visit "/mysite_core/test-config-system-site-name" | ||
| Then the response status code should be 200 | ||
| And the response should contain "Overridden Site Name" | ||
|
|
||
| @api @disable-config-override:system.site | ||
| Scenario: With @disable-config-override, the SUT serves the original stored config value | ||
| When I visit "/mysite_core/test-config-system-site-name" | ||
| Then the response status code should be 200 | ||
| And the response should contain "Drush Site-Install" | ||
| And the response should not contain "Overridden Site Name" | ||
|
|
||
| @api | ||
| Scenario: Visiting a page without the tag sends no X-Config-No-Override header | ||
| When I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should not contain "system.site" | ||
|
|
||
| @api @disable-config-override:system.site | ||
| Scenario: A single @disable-config-override tag sets the X-Config-No-Override header | ||
| When I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should contain "system.site" | ||
|
|
||
| @api @disable-config-override:system.site @disable-config-override:myconfig.settings | ||
| Scenario: Multiple @disable-config-override tags set a comma-separated X-Config-No-Override header | ||
| When I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should contain "system.site,myconfig.settings" | ||
|
|
||
| @api @disable-config-override:system.site | ||
| Scenario: The X-Config-No-Override header survives a login step that resets headers | ||
| Given users: | ||
| | name | mail | roles | status | | ||
| | test_user | test_user@example.com | administrator | 1 | | ||
| When I am logged in as "test_user" | ||
| And I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should contain "system.site" | ||
|
|
||
| @api @disable-config-override:system.site @behat-steps-skip:configOverrideBeforeScenario | ||
| Scenario: The @behat-steps-skip:configOverrideBeforeScenario tag bypasses the trait entirely | ||
| When I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should not contain "system.site" | ||
|
|
||
| @api @disable-config-override:system.site @behat-steps-skip:configOverrideBeforeStep | ||
| Scenario: The @behat-steps-skip:configOverrideBeforeStep tag keeps tag parsing but skips header propagation | ||
| Given users: | ||
| | name | mail | roles | status | | ||
| | test_user2 | test_user2@example.com | administrator | 1 | | ||
| When I am logged in as "test_user2" | ||
| And I visit "/mysite_core/test-config-no-override-header" | ||
| Then the response status code should be 200 | ||
| And the response should not contain "system.site" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.