## Execution & Testing * Authoring tests: follow docs/agents/writing-tests.md (base classes, factory helpers, fixture conventions). * Run Unit Tests: Execute tests locally using the following example command: ./run-unit-tests.sh extensions/CirrusSearch/tests/phpunit/unit/Dispatch/SemanticSearchQueryRouteTest.php * API-Centric Testing: Focus test cases on public APIs. Ensure the surface area of the implementation is verified under various edge cases and assumptions. * Meaningful Coverage: Prioritize the verification of functional correctness over 100% line coverage. Tests should demonstrate that the contract of the public API is honored. ## PHPUnit Test Runner Mechanics (`run-unit-tests.sh`) * The script runs phpunit inside the `mediawiki` docker container. Pass paths relative to the MediaWiki core root, e.g. `extensions/CirrusSearch/tests/phpunit/...`. The container path `/var/www/html/w` maps to the host core checkout (e.g. `~/git/gerrit/mediawiki/core`); translate container paths in stack traces accordingly. * Test layers: unit (`tests/phpunit/unit/`) has no DB or OpenSearch and runs without MediaWiki settings; integration (`tests/phpunit/integration/`) has a real DB but no OpenSearch and needs `PHPUNIT_WIKI` (the script defaults it to `cirrustestwiki`); Cucumber (`tests/integration/features/`) is the only layer with a live index. Cucumber has its own runner — `./run-integration.sh` (see below) — so it is simply out of scope for *this* script, NOT unavailable. "Not run by `run-unit-tests.sh`" never means "cannot be run." * You can pass multiple test files in one invocation to run them together. ## Cucumber Integration Test Runner Mechanices (`./run-integration.sh`) * A live OpenSearch environment IS available locally and you CAN run Cucumber. When an issue lists Cucumber acceptance criteria, running them is your job — author the scenario, run `./run-integration.sh`, and report the result. Do NOT mark Cucumber criteria "deferred to the live-index suite" or "not exercised by `run-unit-tests.sh`"; that is the unit runner's scope, not a statement about availability. Deferral is allowed ONLY when an explicit, prior environment failure proves the runner is broken — record that failure, don't assume it. * The script runs cucumbre inside the mediawiki cli docker infra. Test selection is controlled by an env var defaulting to`CIRRUS_FEATURES="*.feature"`, e.g. `CIRRUS_FEATURES="redirect_documents_api.feature" ./run-integration.sh` to run one feature. * The related script `./create-env.sh` resets the complete integration environment, otherwise database and indexes are preserved between test runs. * All feature files are suffixed `_api.feature` or `_browser.feature` (by transport); name new files accordingly. ## Fixture-Based Tests (`assertFileContains` + `*.expected`) * Many tests (e.g. `SearcherTest`, `MappingConfigBuilderTest`, `MoreLikeFeatureTest`) compare generated output against committed `tests/phpunit/fixtures/**/*.expected` files. * `CIRRUS_REBUILD_FIXTURES` controls rebuild mode; the script defaults it to `yes`. Use `CIRRUS_REBUILD_FIXTURES=no` to verify against existing fixtures (the normal "did I break anything" run). * IMPORTANT: `assertFileContains` only writes a fixture when the file is **missing** — it never overwrites. To regenerate changed fixtures: (1) `find ... -name '*.expected' -delete` the affected files, (2) run with `CIRRUS_REBUILD_FIXTURES=yes` (recreates them, marks those tests skipped), (3) run again with `CIRRUS_REBUILD_FIXTURES=no` to confirm they pass. Always `git diff` the regenerated fixtures to confirm the change is what you intended. * `randomizeFixtures` samples only a random subset of cases per verification run (seed-based), so a passing `CIRRUS_REBUILD_FIXTURES=no` run does not exercise every fixture. When rebuilding (`=yes`) it returns ALL cases, so deleting + rebuilding regenerates the full set consistently — prefer regenerating the whole affected set over individual files. * A change to a widely-assembled query (e.g. anything in `SearchContext::getQuery()`) will ripple through hundreds of `searchText/*.expected` fixtures; this is expected and produces a large but uniform diff. ## Workspace Etiquette * Keep the Working Directory Clean: Leave all git operations (adding files, staging, or committing) to the human operator. * Preserve Diff Integrity: Limit your actions to file modifications only. This allows the lead to use git diff to review and validate the full scope of your architectural changes before they are finalized. * Internationalization: When adding i18n messages developers are required to populate en.json and qqq.json ## Test Construction & Dependency Injection * Prefer Real Objects: Use actual implementation objects whenever possible to ensure tests reflect real-world behavior. * Use Static Factories: Utilize existing test-specific static methods for dependency setup. * Example: Use CirrusDebugOptions::forSemanticSearchUnitTests() instead of creating a mock object. * Minimize Mocking: Resort to mocking only for external services or complex infrastructure that cannot be instantiated in a unit test environment. High-fidelity state is preferred over simulated behavior. ## Agent skills ### Issue tracker Issues and PRDs live as local markdown files under `.scratch//`. See `docs/agents/issue-tracker.md`. ### Triage labels Default vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`), recorded as `Status:` lines in each issue file. See `docs/agents/triage-labels.md`. ### Domain docs Single-context: `CONTEXT.md` + `docs/adr/` at the repo root. See `docs/agents/domain.md`.