Testing Guide
This document describes local and CI testing for drupalforge_deploy.
Audience
This document is for testers running local and suite-level validation.
For other audiences: - Users/site administrators: README.md - Developers/maintainers: TODO.md, DESIGN.md, CHANGELOG.md - AI contributors/agents: AGENTS.md - CI operators (pipeline architecture and job orchestration): docs/ci-architecture.md
Documentation in this guide is maintained using Drupal documentation standards and best practices.
Quick Start
To run tests locally for the first time:
- Bootstrap the runtime:
bash scripts/testing/bootstrap-runtime.sh - Run Unit or Kernel tests:
bash scripts/testing/run-phpunit.sh tests/src/Unit/ - Run Functional tests:
bash scripts/testing/run-functional.sh tests/src/Functional/ - Run the pre-push gate:
bash scripts/testing/run-tests.sh --changed
Drupal Version Selection
Local tests run against the Drupal version that is installed in your local environment.
The repository keeps drupal/core-dev aligned to the active Drupal testing line so local
dependencies stay compatible with the Drupal GitLab CI template output. The installed version
is therefore determined by the compatible combination of drupal/drupal and drupal/core-dev
that resolves when you run composer install.
To switch the local Drupal version, update both packages together with compatible constraints:
composer require --dev drupal/core-dev:^10.0
composer update drupal/drupal:^10.0 drupal/core-dev:^10.0
Then rebuild the local runtime to use the newly installed version:
bash scripts/testing/bootstrap-runtime.sh
Substitute ^10.0 with any supported compatible line (for example ^11.0). Keep
drupal/drupal and drupal/core-dev on the same supported line to avoid missing
BrowserTest and Kernel-test dependencies in local and CI environments.
Session-Scoped Focused Runs (Agent Workflow)
Use focused runners for the current session's changes. Do not use repo-diff checks as the default session workflow.
Bootstrap runtime once in a fresh workspace:
bash scripts/testing/bootstrap-runtime.sh
Run focused Unit/Kernel targets:
bash scripts/testing/run-phpunit.sh tests/src/Unit/DeploymentUrlBuilderTest.php
bash scripts/testing/run-phpunit.sh tests/src/Unit/BackupMigrateDestinationMetadataProviderTest.php
bash scripts/testing/run-phpunit.sh tests/src/Kernel/DeployServiceWiringKernelTest.php --filter testDeployServiceWiring
Focused PHPUnit runner hardening:
- scripts/testing/run-phpunit.sh now safely supports invocations with no extra PHPUnit arguments on macOS Bash as well as Linux shells.
- Use --filter only when you want to narrow execution, not as a shell-expansion workaround.
Run focused Functional targets:
bash scripts/testing/run-functional.sh tests/src/Functional/DeployRouteAccessTest.php
bash scripts/testing/run-functional.sh tests/src/Functional/DeployRouteAccessTest.php --filter testStep4AutoPopulatesDeploymentEnvironmentVariables
bash scripts/testing/run-functional.sh tests/src/Functional/DeployRouteAccessTest.php --filter testAdminAccess
Handoff rules for agents — see AGENTS.md for the full protocol. Key points:
- Required (auto-run): Before handoff, automatically run session-scoped focused validation
for the current slice (focused
run-phpunit.shand/orrun-functional.shruns for affected code paths). - Optional (offer, do not auto-run): Offer unstaged-change validation only when no
user-approval steps remain, using exactly:
If you like, I can run a quick check on files that have changed but not been staged yet. - Placement: In handoff output, provide the work summary first, then a blank line, then the optional statement. Emit one blank line after the optional statement as a safety buffer. Do not add hand-authored text after it.
- Scope: The optional statement covers only unstaged working-tree changes
(
--changed-unstaged), excluding staged changes and all un-pushed commits.
Repo-diff validation (--changed-unstaged) is reserved for pre-push hooks and explicit
user-requested manual runs. Do not auto-run it during agent handoff.
Test Execution Paths
Use these entry points consistently:
| Purpose | Command | Notes |
|---|---|---|
| Bootstrap focused local runtime once | bash scripts/testing/bootstrap-runtime.sh |
Prepares the runtime build directory ($DRUPALFORGE_RUNTIME_BUILD_DIR, default vendor/drupal/drupal), links the module, and prepares BrowserTest output directories. |
| Run focused Unit or Kernel coverage | bash scripts/testing/run-phpunit.sh <target> |
Use for session-scoped PHPUnit work; Kernel runs auto-set SIMPLETEST_DB when needed. |
| Run focused Functional coverage | bash scripts/testing/run-functional.sh <target> |
Starts a local web server, probes readiness, and sets BrowserTest output paths automatically. |
| Run the repository validation gate | scripts/testing/run-tests.sh --changed |
Use for pre-push style validation of the current branch changes. |
| Run the full repository suite | scripts/testing/run-tests.sh |
Comprehensive local validation; slower than changed-mode. |
| Run the pre-push readiness checklist | scripts/testing/ready-to-run.sh |
Structural and documentation-oriented pre-push checklist with optional changed/full test execution. |
| Reset the PHPUnit failure baseline | scripts/testing/reset-phpunit-baseline.sh --yes |
Re-runs suites, rebuilds failure-baseline.json, and prunes keys that reached zero. |
| Reset the yamllint baseline | scripts/testing/reset-yamllint-baseline.sh --yes |
Re-runs yamllint key extraction and rewrites yamllint-baseline.txt. |
| Reset the shellcheck baseline | scripts/testing/reset-shellcheck-baseline.sh --yes |
Re-runs shellcheck key extraction and rewrites shellcheck-baseline.txt. |
Test Script Hierarchy (Line Graph)
Use this as the canonical invocation graph for local execution.
git push
|
v
.githooks/pre-push
|
v
scripts/testing/ready-to-run.sh
|
+--> scripts/testing/check-orphan-test-assets.sh
|
+--> scripts/testing/run-tests.sh --changed (default path)
|
+--> scripts/testing/run-tests.sh (--full path)
Manual focused path (session-scoped):
scripts/testing/bootstrap-runtime.sh
|
+--> scripts/testing/run-phpunit.sh <Unit|Kernel target>
|
+--> scripts/testing/run-functional.sh <Functional target>
Bootstrap placement in the hierarchy:
- Explicit for focused/manual runs: run scripts/testing/bootstrap-runtime.sh first, then focused runners.
- Implicit for repository gate runs: scripts/testing/run-tests.sh bootstraps runtime automatically when PHP/Drupal validation requires it.
Local Pre-push Gate
Repository-managed hooks are in .githooks.
One-time setup:
git config core.hooksPath .githooks
chmod +x .githooks/pre-push scripts/testing/run-tests.sh scripts/testing/ready-to-run.sh scripts/testing/check-orphan-test-assets.sh scripts/testing/reset-phpunit-baseline.sh scripts/testing/reset-yamllint-baseline.sh scripts/testing/check-yamllint-baseline.sh scripts/testing/reset-shellcheck-baseline.sh scripts/testing/check-shellcheck-baseline.sh
Default push behavior:
- pre-push runs scripts/testing/ready-to-run.sh (wrapper), which runs changed-files mode by default
- JavaScript, CSS, Markdown, YAML, shell, and documentation link checks run only when matching files changed
- PHP syntax validation runs when PHP files changed
- coding standards (phpcs) fail only on changed-line violations
- static analysis (phpstan) runs full scan
- PHPUnit runs impacted suites where detectable
Additional local prerequisites for the expanded validation gate:
- Node.js and npm for eslint, stylelint, and markdownlint-cli2
- yamllint for YAML validation
- shellcheck for shell validation
Install the JavaScript-based lint dependencies from repository root:
npm ci
Example macOS/Homebrew setup for required non-PHP CLIs:
brew install shellcheck yamllint node
Force full checks:
DRUPALFORGE_HOOK_FULL=1 git push
Run full checks directly:
scripts/testing/run-tests.sh
Run changed-mode checks directly:
scripts/testing/run-tests.sh --changed
Use this command as a repo-diff check when needed (for example pre-push or manual user validation). It is not session-specific and can include changes from other local work.
Changed-mode skip behavior: - If only documentation files changed, the hook runs Markdown linting and repository docs link checks without bootstrapping Drupal. - If only JS, CSS, YAML, or shell files changed, the hook runs the matching linters and skips Drupal runtime bootstrapping unless PHP or Drupal YAML validation is also required. - If PHP or Drupal YAML files changed, the hook additionally runs PHP syntax validation, Drupal coding standards, PHPStan, and impacted PHPUnit suites.
Run individual validation commands from repository root when needed:
php scripts/testing/check-doc-links.php
php $(git ls-files '*.php' '*.module' '*.inc' '*.install' '*.profile' '*.theme' | head -n 1) -l
npm exec -- eslint --max-warnings=0 js/**/*.js
npm exec -- stylelint css/**/*.css
npm exec -- markdownlint-cli2 README.md AGENTS.md CHANGELOG.md TODO.md docs/**/*.md
yamllint -s -c .yamllint $(git ls-files '*.yml' '*.yaml')
bash scripts/testing/check-yamllint-baseline.sh
bash scripts/testing/check-shellcheck-baseline.sh
Documentation link checking scope:
- Validates repository-local relative links in Markdown sources.
- Ignores external URLs (https, mailto, tel, data, javascript) to keep the check deterministic in local and CI environments.
Runtime bootstrap prerequisite
The hook runner bootstraps a Drupal project in $DRUPALFORGE_RUNTIME_BUILD_DIR (default vendor/drupal/drupal). In environments where Composer cannot fetch GitHub
distribution archives anonymously, dependency installation may fail with authentication
errors (for example: Could not authenticate against github.com).
If required contrib modules are missing from the repository vendor/ tree, runtime bootstrap attempts a one-time repository dependency install (composer install) automatically before failing.
Bootstrap now runs a preflight gate before Composer operations to fail early on:
- missing required commands (git, php, composer)
- missing required PHP extensions (intl, json, libxml, mbstring, openssl, pdo, xml, zip)
- unwritable runtime paths ($DRUPALFORGE_RUNTIME_BUILD_DIR parent and COMPOSER_CACHE_DIR when set)
Notes:
- Focused and repository runners load PHPUnit configuration from repository-root phpunit.xml.dist.
- PHPUnit execution is routed through a repository wrapper (scripts/testing/phpunit-wrapper.php) and runtime-aware autoload helper (scripts/testing/phpunit-autoload.php) so both the main process and isolated child processes bootstrap Drupal from vendor/drupal/drupal instead of the root drupal/core package.
- The repository PHPUnit bootstrap (scripts/testing/phpunit-bootstrap.php) pre-populates extension namespaces with realpath de-duplication, remaps PSR-4 prefixes for Drupal core to the runtime tree, preloads runtime Drupal\Core\Test traits needed by Browser tests, and then delegates to Drupal core bootstrap.
- Static analysis and coding-standard tools are installed from the repository composer.json and executed from vendor/bin (for example phpcs, phpstan, and phpunit).
- When local Composer tooling expects a scaffolded root core/ path after dependency refresh, scripts/testing/run-tests.sh creates a temporary minimal core/ tree linked to runtime includes, lib, and core.services.yml so PHPCS, PHPStan, and PHPUnit can run without a full scaffolded Drupal root.
If preflight detects an issue, bootstrap exits with a focused remediation hint before attempting dependency installation.
The runtime contract for this module is now explicit: tests must exercise Backup and
Migrate with backup_migrate_aws_s3. The local and CI bootstrap should install the
real required module set rather than creating Flysystem-related stub modules.
If this occurs, configure a GitHub token for Composer before running runtime suites:
composer config --global github-oauth.github.com <token>
Tokenless environments are supported with best-effort fallback guidance. Runtime
bootstrap now:
- detects whether GitHub OAuth is configured
- uses --prefer-dist for Composer create/require operations
- classifies common Composer failure categories (auth, network, resolver)
- prints non-token recovery options when authentication/rate-limit failures occur
For partially initialized runtime directories (for example interrupted bootstrap), bootstrap now fails fast with explicit cleanup/retry guidance:
rm -rf "$DRUPALFORGE_RUNTIME_BUILD_DIR" && bash scripts/testing/bootstrap-runtime.sh
After configuring credentials, rerun:
scripts/testing/run-tests.sh
Kernel and functional PHPUnit suites also require Drupal Simpletest environment
variables when executed directly via phpunit:
SIMPLETEST_DB(database connection string for test site installs)SIMPLETEST_BASE_URL(base URL for BrowserTestBase/functional tests)
Without these values, kernel/functional suites fail early with environment configuration exceptions.
Repository runner behavior (scripts/testing/run-tests.sh):
- Automatically sets SIMPLETEST_DB when not provided (default SQLite test DB string).
- Automatically sets BROWSERTEST_OUTPUT_DIRECTORY to $DRUPALFORGE_RUNTIME_BUILD_DIR/sites/simpletest/browser_output and ensures that directory exists and is writable before Functional suite execution (HTML/screenshot output for failed BrowserTestBase tests).
- Automatically starts/stops a local functional web server and probes readiness before Functional suite.
- Accepts overrides via SIMPLETEST_DB, SIMPLETEST_BASE_URL, and DRUPALFORGE_TEST_SERVER_PORT.
PHPUnit Failure Baseline Workflow
Baseline workflow for PHPUnit suites: - Baseline file stores expected failing-test counts for Unit/Kernel/Functional suites. - Changed-line policy is strict: failures mapped to changed lines are blockers regardless of baseline counts. - Count regressions are strict: new or increased failing-test keys are blockers. - Count decreases require explicit baseline reset before validation passes. - Baseline reset prunes keys that reach zero; later reappearance of those keys is treated as a new regression.
Current baseline files:
- scripts/testing/baselines/failure-baseline.json is the source of truth for expected failing-test counts.
Changed-line failure handling:
- scripts/testing/lib/phpunit-baseline-check.php attempts to map PHPUnit failures to a source path and line.
- When a failure is attributed to a changed line in the current validation scope, validation fails even if that failure key already exists in the baseline.
- When exact line attribution is unavailable but the failure maps to a changed file with no reliable line number, the changed-file hit is treated conservatively and still blocks.
Count-based failure handling: - Unchanged counts pass. - New failing-test keys block. - Increased counts for an existing key block. - Decreased counts also block until the baseline is reset so the repository-tracked baseline remains explicit and deterministic.
Baseline reset behavior:
- scripts/testing/reset-phpunit-baseline.sh --yes re-runs Unit, Kernel, and Functional suites.
- The script rebuilds failure-baseline.json from observed failing-test keys and counts.
- Keys that drop to zero are removed naturally during reset.
- If a previously removed key reappears in a later run, it is treated as a new regression and blocks validation.
Reset baseline explicitly:
scripts/testing/reset-phpunit-baseline.sh --yes
Yamllint Baseline Workflow
Yamllint enforcement is count-based:
- scripts/testing/baselines/yamllint-baseline.txt stores expected violation counts per file|rule key.
- scripts/testing/check-yamllint-baseline.sh compares current yamllint findings (aggregated as counts) to the baseline and blocks on bidirectional drift.
- New or increased violation counts block.
- Decreased counts also block until baseline is reset so the repository-tracked baseline remains explicit and deterministic.
- An independent changed-lines gate blocks any yamllint violation whose line falls within the diff of the current change. The changed-lines gate cannot be reset via baseline reset.
Reset yamllint baseline explicitly:
Additionally, scripts/testing/check-yamllint-baseline.sh accepts an optional --changed-lines-json <file> argument when invoked from the runner to activate the changed-lines gate. When called standalone (without the argument), only the count-based drift gate applies.
scripts/testing/reset-yamllint-baseline.sh --yes
Shellcheck Baseline Workflow
Shellcheck enforcement is count-based:
- scripts/testing/baselines/shellcheck-baseline.txt stores expected violation counts per file|SCxxxx key.
- scripts/testing/check-shellcheck-baseline.sh compares current shellcheck findings (aggregated as counts) to the baseline and blocks on bidirectional drift.
- New or increased violation counts block.
- Decreased counts also block until baseline is reset so the repository-tracked baseline remains explicit and deterministic.
- An independent changed-lines gate blocks any shellcheck violation whose line falls within the diff of the current change. The changed-lines gate cannot be reset via baseline reset.
Reset shellcheck baseline explicitly:
Additionally, scripts/testing/check-shellcheck-baseline.sh accepts an optional --changed-lines-json <file> argument when invoked from the runner to activate the changed-lines gate. When called standalone (without the argument), only the count-based drift gate applies.
scripts/testing/reset-shellcheck-baseline.sh --yes
Code Coverage
Generate HTML and Clover XML coverage reports for all test suites during full test runs:
DRUPALFORGE_COVERAGE=1 scripts/testing/run-tests.sh
Coverage reports are written to:
- coverage/ directory (local, excluded from git)
- HTML reports: coverage/Unit/index.html, coverage/Kernel/index.html, coverage/Functional/index.html
- Clover XML: coverage/Unit.clover.xml, coverage/Kernel.clover.xml, coverage/Functional.clover.xml
View HTML reports in a browser:
open coverage/Unit/index.html
Coverage is not generated during --changed mode to keep development iteration fast.
CI Testing
Local parallel execution
scripts/testing/run-tests.sh runs validation lanes in parallel where safe:
- All non-PHP linters (ESLint, stylelint, markdownlint, yamllint, shellcheck, docs links) run concurrently.
- PHP syntax checks run concurrently per file.
- After runtime bootstrap, PHPCS and PHPStan run in parallel.
- Unit and Kernel PHPUnit suites run in parallel after standards pass.
- Functional suite runs after Unit and Kernel complete.
In changed mode (--changed), the changed-lines diff map is prepared before linters run so all validators (including shellcheck) have access to it for the changed-lines blocking gate.
CI Testing
GitLab CI runs:
- Drupal GitLab Templates baseline includes
- modular test jobs in .gitlab-ci.yml (standards, static analysis, PHPUnit)
- Drupal bootstrap in $DRUPAL_BUILD_DIR per CI job
- static docs publication via pages job on default branch
- real Backup and Migrate AWS S3 dependency installation for runtime coverage
CI test jobs execute tools directly:
- php -l for PHP syntax validation
- phpcs (Drupal,DrupalPractice)
- phpstan (using phpstan.neon, production module code scope)
- eslint, stylelint, and markdownlint-cli2 for repository asset and documentation linting
- yamllint and shellcheck for YAML and shell validation
- scripts/testing/check-doc-links.php for repository-local documentation link checking
- phpunit Unit/Kernel/Functional suites
CI pipeline job graph (see ci-architecture.md for full details):
drupal_ci_sanity
│
drupal_linters (lint/docs; no Drupal bootstrap)
│
├── drupal_standards_unit_kernel (PHPCS + PHPStan + Unit + Kernel; parallel)
└── drupal_tests_functional (Functional suite; parallel)
│
drupal_coverage_report (aggregates coverage artifacts; enforces threshold)
drupal_ci_sanity: validates bootstrap prerequisites before longer jobs run.drupal_linters: lint and docs checks without a Drupal bootstrap; fast failure gate.drupal_standards_unit_kernelanddrupal_tests_functionalrun in parallel after linting passes, each with a full Drupal bootstrap.drupal_coverage_report: aggregates Clover XML coverage from both test jobs and enforces the coverage threshold.- Combined coverage metric exported as
TOTAL_COVERAGEindrupal_coverage_reportlogs. - PHPUnit deprecation reporting output when the installed PHPUnit version supports the flag.
Coverage threshold is enforced via CI variable:
- DRUPALFORGE_CI_COVERAGE_MIN (default 0)
- Example: set to 65 to require at least 65% combined line coverage in primary job
Kernel/functional CI execution uses explicit Simpletest environment values:
- SIMPLETEST_DB=sqlite://localhost/sites/default/files/simpletest.sqlite
- SIMPLETEST_BASE_URL=${DRUPALFORGE_TEST_BASE_URL} (functional suite)
CI stability controls:
- Composer bootstrap/install commands are retried (DRUPALFORGE_CI_RETRY_ATTEMPTS, default 3) to reduce transient network failures.
- Functional test web server startup is probed before running BrowserTestBase suites.
- Functional server endpoint is configurable via:
- DRUPALFORGE_TEST_SERVER_PORT (default 8888)
- DRUPALFORGE_TEST_BASE_URL (default http://127.0.0.1:8888)
- PHPStan memory usage is configurable via DRUPALFORGE_PHPSTAN_MEMORY_LIMIT (default 512M) for large analysis runs.
Composer advisory bypass (default on for test bootstrap):
- Local runner defaults to DRUPALFORGE_COMPOSER_ALLOW_INSECURE=1 for temporary runtime bootstrap.
- CI defaults to DRUPALFORGE_COMPOSER_ALLOW_INSECURE=1 for bootstrap stability.
- Set DRUPALFORGE_COMPOSER_ALLOW_INSECURE=0 to restore strict advisory blocking.
Compatibility jobs run tests against Drupal core majors:
- drupal_compatibility_8
- drupal_compatibility_9
- drupal_compatibility_10
- drupal_compatibility_11
Ready to run (before push to GitLab):
- scripts/testing/ready-to-run.sh passes (or run with --no-tests for structure-only validation).
- scripts/testing/run-tests.sh --changed passes.
- scripts/testing/run-tests.sh is successful in local environment, or blockers are explicitly documented in TODO.md.
- Completed validation work is moved from TODO.md to CHANGELOG.md before commit.
- .gitlab-ci.yml, docs/testing.md, and docs/ci-architecture.md are consistent for variables/jobs/tooling.
- GitLab CI Lint validation passes in the project Pipeline editor (Validate tab):
- run Lint CI/CD sample to validate merged configuration syntax (including include expansion)
- run Simulate pipeline creation for the default branch to catch stage/needs/rules graph issues
- Modified files have no unresolved diagnostics.
CI hardening completion checklist:
- drupal_ci_sanity succeeds in normal pipelines and fails clearly when prerequisites are intentionally broken.
- drupal_standards_and_tests publishes coverage/ artifacts and logs TOTAL_COVERAGE.
- DRUPALFORGE_CI_COVERAGE_MIN enforcement is validated with one passing and one failing threshold run.
- All compatibility jobs complete at least one green run after retry/readiness controls are enabled.
- Functional server failures (if any) include coverage/drupal-test-server.log for diagnosis.
Primary CI file:
- .gitlab-ci.yml
Planned Test Layers
- Unit: service logic, URL composition, mapping rules
- Kernel: config/service wiring and backup metadata handling
- Functional: admin route behavior and permission checks
Initial kernel/functional coverage now targets:
- service wiring sanity checks for deploy-related services
- deploy/settings route access behavior for admin vs unauthorized users
- safe handling of absent/empty deployment_env_vars config in deploy URL composition path
- config API round-trip behavior for persisted deployment_env_vars values
- logger channel service wiring for deploy/runtime diagnostics
- deploy form render-path fallback when backup discovery throws unexpected exceptions
Current unit focus also includes backup catalog normalization rules: - expiry detection based on metadata timestamps - fallback handling for missing labels/timestamps - deterministic ordering (newest first) - provider integration path (AWS S3 plugin loading + exception-safe fallback) - AWS S3 plugin-instantiation/listing failures emit warning-level diagnostics with safe context - catalog-level provider exceptions emit warning diagnostics and return stable empty discovery output - AWS S3-only destination discovery and readiness semantics - deploy backup option formatting/default-selection helper behavior - valid-only backup selection option generation - deploy readiness evaluator behavior (git + backup preconditions) - deploy readiness evaluator failure-reason messages
Phase 3 unit coverage targets:
- deployment URL parameter composition (DP_REPO_BRANCH, DP_IMAGE)
- repository URL trailing-slash normalization in DP_REPO_BRANCH
- selected branch whitespace trimming in DP_REPO_BRANCH
- deterministic parameter ordering and RFC3986 encoding
- selected-branch handling for branch names containing /
- settings-provided deployment environment variable parsing (KEY=VALUE lines)
- invalid env variable keys are filtered from URL output
- required launch keys cannot be overridden by env variable input
- duplicate env variable keys use last-value-wins parsing
- whitespace-only required launch inputs are rejected with validation errors
- launch URL output remains HTTPS/TLS transport
Phase 3 functional coverage target:
- deploy step renders Deploy as a link (not submit) and keeps Save selection as persistence-only submit action
- deploy step renders raw URL preview separately from the editable env-style payload textarea
- save-selection flow persists branch/backup defaults that are reflected after page reload (no-JS fallback)
- deploy link and preview values update from selected branch/backup values (plus JS-enhanced textarea edits when enabled)
- deploy form rejects whitespace-only branch input and does not render launch URL success output
- deploy page shows unsupported-remote readiness guidance when Git provider is not GitHub/GitLab
- deploy page shows detached-HEAD readiness guidance when repository is not on an active branch
- deploy page shows no-available-backup readiness guidance when discovered backups are expired or otherwise non-selectable
- deploy page shows no-compatible-provider guidance when destination integration is unavailable
- deploy page shows no-backups-discovered guidance when backup catalog is empty
- deploy submit action renders disabled when readiness prerequisites are not met
- deploy submit action renders enabled when readiness prerequisites are met
- deploy page shows temporary private-repository unsupported warning text
- deploy submit failure path emits warning log records on launch URL generation exceptions
- deploy route remains render-safe (HTTP 200) when backup discovery throws unexpected exceptions
- deploy route-level backup discovery exceptions emit warning log records during fallback rendering
MinIO Notes
Current automated test behavior:
- The default Unit, Kernel, and Functional suites do not start MinIO or perform live S3 I/O.
- Local and CI bootstrap install the real
backup_migrate_aws_s3module so runtime wiring matches production expectations. - Functional tests then replace deploy-facing services through the
drupalforge_deploy_test_supportdecorators, which supply state-driven backup fixtures instead of reading from object storage. - Unit coverage for
BackupMigrateDestinationMetadataProvideruses mocked plugin/container behavior to validate AWS S3 plugin loading, normalization, and exception handling without a live endpoint.
Use MinIO when you need manual or future integration validation against a real S3-compatible endpoint for backup_migrate_aws_s3.
Manual MinIO validation
Prerequisites:
- Docker or another way to run MinIO locally.
- A local Drupal runtime where you can configure Backup and Migrate AWS S3.
- Confirmation that the installed
backup_migrate_aws_s3version supports a custom S3 endpoint suitable for MinIO.
Start MinIO locally:
mkdir -p .minio-data
docker run --rm \
--name drupalforge-minio \
-p 9000:9000 \
-p 9001:9001 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
-v "$PWD/.minio-data:/data" \
quay.io/minio/minio server /data --console-address ":9001"
Create a deterministic test bucket:
docker run --rm --network host quay.io/minio/mc \
alias set local http://127.0.0.1:9000 minioadmin minioadmin
docker run --rm --network host quay.io/minio/mc \
mb --ignore-existing local/drupalforge-deploy-test
Seed deterministic fixture objects:
mkdir -p .minio-fixtures
printf 'fixture backup 1\n' > .minio-fixtures/2026-03-18T120000Z-site.sql.gz
printf 'fixture backup 2\n' > .minio-fixtures/2026-03-18T121500Z-site.sql.gz
docker run --rm --network host -v "$PWD/.minio-fixtures:/fixtures:ro" quay.io/minio/mc \
cp /fixtures/2026-03-18T120000Z-site.sql.gz local/drupalforge-deploy-test/2026-03-18T120000Z-site.sql.gz
docker run --rm --network host -v "$PWD/.minio-fixtures:/fixtures:ro" quay.io/minio/mc \
cp /fixtures/2026-03-18T121500Z-site.sql.gz local/drupalforge-deploy-test/2026-03-18T121500Z-site.sql.gz
Configure Drupal for manual validation:
- Enable and configure
backup_migrateandbackup_migrate_aws_s3in your local Drupal site. - Add a Backup and Migrate destination of type AWS S3.
- If the contrib module version supports it, point the destination at
http://127.0.0.1:9000using path-style access and the MinIO credentials shown above. - Use the bucket name
drupalforge-deploy-testand keep object names deterministic so backup ordering and readiness behavior are easy to verify. - Confirm Backup and Migrate can list the seeded objects before testing
drupalforge_deploy.
Validate drupalforge_deploy behavior manually:
- Open
/admin/config/development/drupalforge-deploy. - Confirm the readiness checklist shows the AWS S3 destination as available.
- Confirm seeded MinIO objects appear as deployable backups.
- Generate a launch URL and verify backup selection and branch selection behave as expected.
- Repeat with an intentional failure, such as wrong credentials or a missing bucket, and confirm readiness guidance remains actionable.
Notes:
- This MinIO flow is manual validation only today; it is not part of
scripts/testing/run-tests.shor the default GitLab CI jobs. - Keep fixture object names and timestamps stable so ordering assertions are reproducible when a dedicated MinIO-backed suite is added later.
- If the installed
backup_migrate_aws_s3release does not support custom endpoints, treat MinIO testing as blocked until that capability is verified or contributed upstream.