-
-
Notifications
You must be signed in to change notification settings - Fork 28
[#1192] Added drevops/vortex-tooling codebase.
#2115
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
base: develop
Are you sure you want to change the base?
Conversation
WalkthroughAdds a standalone PHP tooling package under Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant CI as GitHub Actions
participant Repo as Repo
participant Router as notify (router)
participant Channel as notify-{channel}
participant Ext as External Service
Dev->>Repo: push / open PR (develop / feature/** / bugfix/**)
Repo->>CI: workflow trigger (.github/workflows/vortex-test-tooling.yml)
CI->>CI: setup PHP matrix (8.3,8.4,8.5), Node.js, install deps
CI->>CI: lint (phpcs/phpstan), run phpunit with coverage, upload artifacts
CI->>Ext: upload cobertura.xml to Codecov
Note over Router,Channel: Runtime flow for notifications
Dev->>Router: run notify (env: VORTEX_NOTIFY_CHANNELS,LABEL,EVENT)
Router->>Router: load env, validate, parse channels
loop per channel
Router->>Channel: execute notify-{channel} (passthru)
Channel->>Channel: build payload (replace tokens)
Channel->>Ext: send API/mail/webhook
Ext-->>Channel: response (status)
Channel-->>Router: exit code & logs
end
Router->>Dev: aggregated exit (0 if all succeed, else non-zero)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus review on:
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
95b0b22 to
310b5e8
Compare
310b5e8 to
9a69aa1
Compare
9a69aa1 to
482ed6a
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #2115 +/- ##
===========================================
+ Coverage 70.89% 72.86% +1.96%
===========================================
Files 99 105 +6
Lines 5075 5458 +383
Branches 44 44
===========================================
+ Hits 3598 3977 +379
- Misses 1477 1481 +4 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 25
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (37)
.github/workflows/vortex-test-tooling.yml(1 hunks).vortex/CLAUDE.md(9 hunks).vortex/installer/composer.json(1 hunks).vortex/tooling/.gitignore(1 hunks).vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/phpstan.neon(1 hunks).vortex/tooling/phpunit.xml(1 hunks).vortex/tooling/rector.php(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/test_plan.md(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-failing(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-passing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-failing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-passing(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Traits/MockTrait.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-request-post-passing.vortex/tooling/phpunit.xml.vortex/tooling/tests/Fixtures/test-request-multiple.vortex/tooling/tests/Unit/UnitTestCase.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Fixtures/test-request-get-passing.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/tooling/tests/Traits/MockTrait.php.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/tooling/test_plan.md.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Fixtures/test-request-get-failing
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/CLAUDE.md
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/.gitignore.vortex/CLAUDE.md
📚 Learning: 2025-06-02T00:41:36.321Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1697
File: .vortex/installer/tests/Fixtures/install/provision_database_lagoon/docs/onboarding.md:16-17
Timestamp: 2025-06-02T00:41:36.321Z
Learning: DO NOT EVER REVIEW FILES IN .vortex/installer/tests/Fixtures/ directory - these are test fixtures and should not be reviewed under any circumstances.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/CLAUDE.md
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/tests/Fixtures/test-request-post-failing
📚 Learning: 2025-06-03T03:15:29.849Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1714
File: scripts/vortex/doctor.sh:69-70
Timestamp: 2025-06-03T03:15:29.849Z
Learning: In the Vortex project, logging functions (task(), info(), note(), pass(), fail(), warn()) are intentionally duplicated across all script files to make each script self-contained and independent, rather than sharing them through a common library. This design choice prioritizes script independence over reducing code duplication.
Applied to files:
.vortex/CLAUDE.md
🧬 Code graph analysis (8)
.vortex/tooling/tests/Unit/UnitTestCase.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (1)
mockTearDown(63-81)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
__construct(20-26)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
__construct(20-26)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequest(350-352)mockRequestAssertAllMocksConsumed(360-371).vortex/tooling/src/helpers.php (3)
request_get(237-243)request_post(265-272)request(294-347)
.vortex/tooling/tests/Traits/MockTrait.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/src/helpers.php (2)
fail(149-152)quit(367-369)
.vortex/tooling/tests/Self/MockQuitSelfTest.php (5)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (1)
quit(367-369)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
🪛 GitHub Actions: Vortex - Test tooling
.vortex/tooling/tests/Self/MockRequestSelfTest.php
[error] 37-37: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 57-57: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 75-75: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 93-93: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 110-110: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 128-128: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 146-146: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 163-163: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 184-184: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 218-218: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 222-222: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 226-226: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 242-242: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 243-243: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 248-248: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 267-267: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 268-268: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 270-270: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 285-285: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 297-297: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 480-480: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~140-~140: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/CLAUDE.md
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
83-83: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
128-128: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
143-143: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
153-153: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
171-171: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
183-183: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
198-198: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
252-252: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
259-259: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
264-264: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
332-332: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
364-364: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
379-379: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
380-380: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
389-389: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
390-390: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
397-397: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
425-425: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
426-426: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
439-439: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
444-444: Ordered list item prefix
Expected: 1; Actual: 4; Style: 1/1/1
(MD029, ol-prefix)
592-592: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
675-675: Bare URL used
(MD034, no-bare-urls)
676-676: Bare URL used
(MD034, no-bare-urls)
677-677: Bare URL used
(MD034, no-bare-urls)
.vortex/tooling/test_plan.md
11-11: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
86-86: Bare URL used
(MD034, no-bare-urls)
87-87: Bare URL used
(MD034, no-bare-urls)
92-92: Ordered list item prefix
Expected: 1; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
93-93: Ordered list item prefix
Expected: 2; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
94-94: Ordered list item prefix
Expected: 3; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
95-95: Ordered list item prefix
Expected: 4; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
96-96: Ordered list item prefix
Expected: 5; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
97-97: Ordered list item prefix
Expected: 6; Actual: 20; Style: 1/2/3
(MD029, ol-prefix)
98-98: Ordered list item prefix
Expected: 7; Actual: 21; Style: 1/2/3
(MD029, ol-prefix)
99-99: Ordered list item prefix
Expected: 8; Actual: 22; Style: 1/2/3
(MD029, ol-prefix)
100-100: Ordered list item prefix
Expected: 9; Actual: 23; Style: 1/2/3
(MD029, ol-prefix)
101-101: Ordered list item prefix
Expected: 10; Actual: 24; Style: 1/2/3
(MD029, ol-prefix)
102-102: Ordered list item prefix
Expected: 11; Actual: 25; Style: 1/2/3
(MD029, ol-prefix)
103-103: Ordered list item prefix
Expected: 12; Actual: 26; Style: 1/2/3
(MD029, ol-prefix)
113-113: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
134-134: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
176-176: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
197-197: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
.vortex/tooling/scripts_conversion_plan.md
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
17-17: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
43-43: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
48-48: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
59-59: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
68-68: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
92-92: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
93-93: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
103-103: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
112-112: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
126-126: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
139-139: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
140-140: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
146-146: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
147-147: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
167-167: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
182-182: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
278-278: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
283-283: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
386-386: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
393-393: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
394-394: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
395-395: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
396-396: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
397-397: Ordered list item prefix
Expected: 4; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
398-398: Ordered list item prefix
Expected: 5; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
399-399: Ordered list item prefix
Expected: 6; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
401-401: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
402-402: Ordered list item prefix
Expected: 1; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
403-403: Ordered list item prefix
Expected: 2; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
404-404: Ordered list item prefix
Expected: 3; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
405-405: Ordered list item prefix
Expected: 4; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
406-406: Ordered list item prefix
Expected: 5; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
431-431: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
432-432: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
440-440: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
441-441: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
453-453: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
454-454: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
466-466: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
467-467: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
475-475: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
476-476: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
489-489: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
496-496: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
503-503: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-installer (8.4)
🔇 Additional comments (25)
.vortex/tooling/phpstan.neon (1)
5-28: PHPStan configuration is strict and well-scopedLevel 9 with paths limited to
src/testsand targetedignoreErrorsagainst test-only patterns looks solid and conservative; I don’t see issues here..vortex/tooling/tests/Fixtures/test-quit-failing (1)
1-20: Quit fixture matches conventions and behaviorFixture structure (shebang, strict types, namespace, helpers include) and behavior (announce,
quit(1), unreachable error line) are consistent and suitable for testing non-zero exit paths..vortex/installer/composer.json (1)
14-18: Installer metadata correctly points to main vortex repositoryUpdating
homepageandsupportURLs todrevops/vortexis consistent with the project consolidation and leaves runtime/dev requirements untouched..vortex/tooling/.gitignore (1)
1-3: Tooling .gitignore entries are appropriateIgnoring
vendor,composer.lock, and.logsfor the tooling subtree matches its role as an internal dev/test package and keeps generated artifacts out of VCS..vortex/tooling/tests/Fixtures/test-quit-passing (1)
1-20: Fixture looks correct; no further review neededScript shape (namespace, helpers include, message,
quit(0)and unreachable line) matches a “success” quit fixture and is syntactically fine. Not reviewing deeper code‑quality aspects since this is a test fixture.
Based on learnings.vortex/tooling/tests/Fixtures/test-request-get-failing (1)
1-24: Failure GET fixture is consistent with its intentFlow and messaging align with a “request_get expecting failure” scenario; no syntax or obvious runtime issues. Skipping deeper code‑quality review as this is a test fixture.
Based on learnings.vortex/tooling/tests/Fixtures/test-request-post-failing (1)
1-24: POST failure fixture matches described behaviorScript clearly exercises a failing
request_postscenario and logs expected diagnostics; no obvious syntax/runtime issues. Treating this strictly as a fixture and not applying general code‑quality rules.
Based on learnings.vortex/tooling/tests/Fixtures/test-passthru-passing (1)
1-19: Passthru “success” fixture is straightforward and validCommand, messaging, and exit‑code logging align with a passing passthru scenario; no syntax or obvious runtime problems. Not applying broader quality checks since this is a test fixture.
Based on learnings.vortex/tooling/tests/Fixtures/test-request-get-passing (1)
1-24: GET success fixture aligns with helper contractThe script cleanly exercises a successful
request_getcase and logs status, ok flag, and body; nothing stands out as problematic for a fixture. Skipping deeper code‑quality review per fixture guidance.
Based on learnings.vortex/tooling/tests/Fixtures/test-passthru-failing (1)
4-19: Confirm intended exit‑code behavior for the “failing” passthru fixtureThe fixture says “Script should return code 1” but currently runs
passthru('echo "success"', $exit_code), which normally exits with code0. If your tests rely on the real shell exit code, you may want a command that actually fails (e.g.,falseorexit 1). If MockPassthru/MockTrait fully controls$exit_codefor this script, the current command is fine—just confirm it matches the test harness assumptions..vortex/tooling/tests/Fixtures/test-request-post-passing (1)
1-24: POST success fixture is consistent and minimalThe script correctly represents a “POST success” scenario using
request_postand simple logging; no obvious syntax/runtime issues. Treating this as a fixture and not applying broader code‑quality review.
Based on learnings.vortex/tooling/tests/Fixtures/test-request-multiple (1)
1-26: LGTM! Test fixture structure is correct.The fixture correctly exercises multiple request helper functions (request_get, request_post, request) in sequence, with appropriate output for test verification. The structure aligns with the tooling test harness pattern.
.vortex/tooling/tests/Fixtures/test-passthru-multiple (1)
1-23: LGTM! Test fixture correctly exercises multiple passthru calls.The fixture properly demonstrates sequential passthru operations with exit code capture, following the established test fixture pattern for the tooling subsystem.
.vortex/tooling/phpcs.xml (2)
1-16: LGTM! PHPCS configuration with appropriate base and custom rules.The configuration correctly extends the Drupal standard and adds strict types enforcement plus snake_case naming conventions, which align with the tooling subsystem requirements.
27-46: LGTM! Test file exclusions are appropriately configured.The exclusions for long array declarations and missing comments in test files follow best practices, allowing tests to focus on functionality rather than documentation requirements.
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
1-28: LGTM! Exception class correctly enforces non-zero exit code constraint.The implementation is symmetrical with
QuitSuccessExceptionand properly validates that only non-zero exit codes are represented. The exception message clearly indicates the exit code value for test verification..vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
1-28: LGTM! Exception class correctly enforces zero exit code constraint.The implementation properly validates that only exit code 0 is represented, maintaining symmetry with
QuitErrorException. This allows tests to distinguish between successful and failed quit() scenarios..vortex/tooling/src/notify-newrelic (4)
1-76: LGTM! Script structure and environment variable loading follow established patterns.The script correctly includes the shebang, strict types, helper requirements, and override execution. Environment variable loading with fallbacks is properly implemented using the ternary operator pattern.
78-98: LGTM! Validation and pre-deployment skip logic are correct.Required variables are validated, revision auto-generation follows a clear format, and the pre-deployment event is correctly skipped (New Relic only supports post-deployment notifications).
139-143: LGTM! App ID validation correctly handles non-configured environments.The validation appropriately checks for numeric app IDs and gracefully exits with an informative message for non-configured environments, avoiding false failures.
157-176: LGTM! Payload construction and dispatch correctly implement New Relic API requirements.The deployment payload is properly structured, JSON-encoded with appropriate flags, and the response status is validated (expecting 201 for successful creation). Error messages are descriptive.
.github/workflows/vortex-test-tooling.yml (3)
22-51: LGTM! Workflow setup steps are properly configured.Actions are pinned with commit SHAs for security, PHP is configured with coverage support, and all Composer operations correctly specify the
.vortex/toolingworking directory.
59-72: LGTM! Coverage upload configuration is appropriately strict.Both artifact and Codecov uploads are configured to fail if files are missing or if upload errors occur, ensuring test coverage is properly tracked.
18-21: No issues found — PHP 8.5 is officially released and supported.PHP 8.5.0 reached general availability on November 20, 2025, and shivammathur/setup-php@v2 supports PHP 8.5. The matrix configuration will work correctly without failures.
.vortex/tooling/scripts_conversion_plan.md (1)
1-574: LGTM! Documentation accurately describes the tooling conversion plan.The conversion plan thoroughly documents the bash-to-PHP migration strategy, coding patterns, and project structure. The examples and guidelines align with the implemented tooling subsystem introduced in this PR.
Note: The static analysis hints flag markdown formatting issues (missing blank lines, language specifications for code blocks), but these are cosmetic and don't affect the content quality. Consider addressing them for consistency if markdown linting is enforced in CI.
| - name: Run tests | ||
| run: composer test-coverage | ||
| working-directory: .vortex/tooling | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }} | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Clarify GITHUB_TOKEN environment variable naming.
Line 57 sets the environment variable GITHUB_TOKEN but uses the secret PACKAGE_TOKEN. If the tooling tests expect GITHUB_TOKEN specifically, ensure the secret name matches. If PACKAGE_TOKEN is intentional (e.g., for higher privileges), the env var name may be confusing.
Consider renaming for clarity:
env:
- GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
+ PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }}Or document why GITHUB_TOKEN uses PACKAGE_TOKEN.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
.github/workflows/vortex-test-tooling.yml lines 53-58: the workflow sets env
GITHUB_TOKEN from secrets.PACKAGE_TOKEN which is confusing or potentially
incorrect; either (A) change the env key to PACKAGE_TOKEN (env: PACKAGE_TOKEN:
${{ secrets.PACKAGE_TOKEN }}) if the tooling expects that secret name, or (B)
change the secret reference to secrets.GITHUB_TOKEN (env: GITHUB_TOKEN: ${{
secrets.GITHUB_TOKEN }}) if tooling needs the standard token, or (C) leave as-is
but add a comment above these lines explaining why PACKAGE_TOKEN is mapped to
GITHUB_TOKEN (e.g., requires elevated privileges), so the intent is clear to
maintainers.
| <file>src</file> | ||
| <file>src/notify</file> | ||
| <file>src/notify-email</file> | ||
| <file>src/notify-github</file> | ||
| <file>src/notify-jira</file> | ||
| <file>src/notify-newrelic</file> | ||
| <file>src/notify-slack</file> | ||
| <file>src/notify-webhook</file> | ||
| <file>tests</file> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider simplifying file specification.
Currently, each src/notify-* directory is listed individually. Since line 17 already includes src, lines 18-24 may be redundant unless PHPCS doesn't recursively scan subdirectories. If recursive scanning is enabled by default, you can remove lines 18-24.
Apply this diff if recursive scanning is sufficient:
<file>src</file>
-<file>src/notify</file>
-<file>src/notify-email</file>
-<file>src/notify-github</file>
-<file>src/notify-jira</file>
-<file>src/notify-newrelic</file>
-<file>src/notify-slack</file>
-<file>src/notify-webhook</file>
<file>tests</file>🤖 Prompt for AI Agents
.vortex/tooling/phpcs.xml around lines 17 to 25: the file list redundantly
enumerates src/notify-* subdirectories even though src is already included; if
PHPCS is configured to scan directories recursively, remove the explicit
src/notify, src/notify-email, src/notify-github, src/notify-jira,
src/notify-newrelic, src/notify-slack, and src/notify-webhook entries (lines
18–24) leaving only <file>src</file> (and <file>tests</file>), otherwise confirm
PHPCS non-recursive behavior and add a comment explaining why the individual
entries must remain.
.vortex/tooling/test_plan.md
Outdated
| **Mock response structure:** | ||
| ```php | ||
| [ | ||
| 'url' => 'https://example.com/api', // Expected URL | ||
| 'method' => 'GET', // Optional: expected method | ||
| 'response' => [ | ||
| 'ok' => true, | ||
| 'status' => 200, | ||
| 'body' => 'response body', | ||
| 'error' => null, | ||
| 'info' => ['http_code' => 200] | ||
| ] | ||
| ] | ||
| ``` | ||
|
|
||
| **Implementation approach:** Mock the low-level curl_* functions (curl_init, curl_exec, curl_getinfo, etc.) used by the HTTP request functions to intercept all request operations. | ||
|
|
||
| ## 2. Test Class Structure (tests/Self/MockRequestSelfTest.php) | ||
|
|
||
| **Direct Tests (14 tests):** | ||
|
|
||
| 1. **testMockRequestGetSuccess** - Basic GET with 200 | ||
| - Mock: url, status 200, body "success" | ||
| - Assert: ok=true, status=200, body contains "success" | ||
|
|
||
| 2. **testMockRequestGetCustomSuccess** - GET with custom values | ||
| - Mock: status 201, custom body, custom info | ||
| - Assert: all custom values returned correctly | ||
|
|
||
| 3. **testMockRequestGetDefaultsSuccess** - GET with minimal mock | ||
| - Mock: only url and status | ||
| - Assert: defaults filled in correctly | ||
|
|
||
| 4. **testMockRequestGetFailure404** - GET with 404 error | ||
| - Mock: status 404, ok=false | ||
| - Assert: ok=false, status=404 | ||
|
|
||
| 5. **testMockRequestGetFailure500** - GET with 500 error | ||
| - Mock: status 500, ok=false | ||
| - Assert: ok=false, status=500 | ||
|
|
||
| 6. **testMockRequestGetNetworkError** - GET with network error | ||
| - Mock: error="Could not resolve host" | ||
| - Assert: error message present, ok=false | ||
|
|
||
| 7. **testMockRequestPostSuccess** - POST with body | ||
| - Mock: POST method, body present, status 201 | ||
| - Assert: ok=true, status=201 | ||
|
|
||
| 8. **testMockRequestPostJsonSuccess** - POST with JSON body | ||
| - Mock: POST with JSON content-type header | ||
| - Assert: request handled correctly | ||
|
|
||
| 9. **testMockRequestCustomMethodSuccess** - PUT/DELETE | ||
| - Mock: method=PUT, status 200 | ||
| - Assert: custom method works | ||
|
|
||
| 10. **testMockRequestMultipleSuccess** - Sequential calls | ||
| - Mock: 3 different responses | ||
| - Assert: each returns correct response in order | ||
|
|
||
| 11. **testMockRequestMultipleMoreCallsFailure** - Too many calls | ||
| - Mock: 2 responses | ||
| - Make: 3 calls | ||
| - Assert: RuntimeException on 3rd call | ||
|
|
||
| 12. **testMockRequestMultipleLessCallsFailure** - Too few calls | ||
| - Mock: 3 responses | ||
| - Make: 2 calls | ||
| - Assert: AssertionFailedError in tearDown | ||
|
|
||
| 13. **testMockRequestFailureArgumentExceptionUrl** - Missing URL validation | ||
| - Mock: response without url key | ||
| - Assert: InvalidArgumentException | ||
|
|
||
| 14. **testMockRequestFailureAssertUnexpectedUrl** - URL mismatch | ||
| - Mock: url="https://example.com" | ||
| - Call: url="https://different.com" | ||
| - Assert: RuntimeException with clear message | ||
|
|
||
| **Script Tests (12 tests):** | ||
|
|
||
| 15. **testMockRequestGetScriptPassingSuccess** - GET through script (uses test-request-get-passing) | ||
| 16. **testMockRequestGetScriptFailingSuccess** - GET 404 through script (uses test-request-get-failing) | ||
| 17. **testMockRequestGetScriptCustomSuccess** - Custom values through script (uses test-request-get-passing) | ||
| 18. **testMockRequestGetScriptDefaultsSuccess** - Defaults through script (uses test-request-get-passing) | ||
| 19. **testMockRequestGetScriptNetworkErrorSuccess** - Network error through script (uses test-request-get-failing) | ||
| 20. **testMockRequestPostScriptPassingSuccess** - POST success through script (uses test-request-post-passing) | ||
| 21. **testMockRequestPostScriptFailingSuccess** - POST failure through script (uses test-request-post-failing) | ||
| 22. **testMockRequestMultipleScriptSuccess** - Multiple calls through script | ||
| 23. **testMockRequestMultipleScriptMoreCallsFailure** - Too many calls through script | ||
| 24. **testMockRequestMultipleScriptLessCallsFailure** - Too few calls through script | ||
| 25. **testMockRequestGetScriptFailureArgumentExceptionUrl** - Missing URL through script | ||
| 26. **testMockRequestGetScriptFailureAssertUnexpectedUrl** - URL mismatch through script | ||
|
|
||
| **Naming Convention:** | ||
| - **Test method names**: Use `Success`/`Failure` to indicate the test pathway (what behavior is being tested) | ||
| - **Fixture names**: Use `passing`/`failing` to describe the fixture behavior (whether it passes or fails) | ||
| - **Request script test naming**: For request tests with multiple methods (get, post, request), the pattern is `testMockRequest[Method]Script[Description][Outcome]` (e.g., `testMockRequestGetScriptPassingSuccess`, `testMockRequestPostScriptFailingSuccess`) | ||
|
|
||
| ## 3. Test Fixtures (tests/Fixtures/) | ||
|
|
||
| **test-request-get-passing:** | ||
| ```php | ||
| #!/usr/bin/env php | ||
| <?php | ||
| declare(strict_types=1); | ||
| namespace DrevOps\VortexTooling; | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| echo 'Script will call request_get' . PHP_EOL; | ||
|
|
||
| $result = request_get('https://example.com/api'); | ||
|
|
||
| echo 'Response status: ' . $result['status'] . PHP_EOL; | ||
| echo 'Response ok: ' . ($result['ok'] ? 'true' : 'false') . PHP_EOL; | ||
|
|
||
| if ($result['ok']) { | ||
| echo 'Response body: ' . $result['body'] . PHP_EOL; | ||
| } | ||
| ``` | ||
|
|
||
| **test-request-get-failing:** | ||
| ```php | ||
| #!/usr/bin/env php | ||
| <?php | ||
| declare(strict_types=1); | ||
| namespace DrevOps\VortexTooling; | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| echo 'Script will call request_get expecting failure' . PHP_EOL; | ||
|
|
||
| $result = request_get('https://example.com/not-found'); | ||
|
|
||
| echo 'Response status: ' . $result['status'] . PHP_EOL; | ||
| echo 'Response ok: ' . ($result['ok'] ? 'true' : 'false') . PHP_EOL; | ||
|
|
||
| if (!$result['ok']) { | ||
| echo 'Request failed as expected' . PHP_EOL; | ||
| } | ||
| ``` | ||
|
|
||
| **test-request-post-passing:** | ||
| ```php | ||
| #!/usr/bin/env php | ||
| <?php | ||
| declare(strict_types=1); | ||
| namespace DrevOps\VortexTooling; | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| echo 'Script will call request_post' . PHP_EOL; | ||
|
|
||
| $result = request_post('https://example.com/api', json_encode(['key' => 'value']), ['Content-Type: application/json']); | ||
|
|
||
| echo 'Response status: ' . $result['status'] . PHP_EOL; | ||
| echo 'Response ok: ' . ($result['ok'] ? 'true' : 'false') . PHP_EOL; | ||
|
|
||
| if ($result['ok']) { | ||
| echo 'POST succeeded' . PHP_EOL; | ||
| } | ||
| ``` | ||
|
|
||
| **test-request-post-failing:** | ||
| ```php | ||
| #!/usr/bin/env php | ||
| <?php | ||
| declare(strict_types=1); | ||
| namespace DrevOps\VortexTooling; | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| echo 'Script will call request_post expecting failure' . PHP_EOL; | ||
|
|
||
| $result = request_post('https://example.com/error', json_encode(['key' => 'value']), ['Content-Type: application/json']); | ||
|
|
||
| echo 'Response status: ' . $result['status'] . PHP_EOL; | ||
| echo 'Response ok: ' . ($result['ok'] ? 'true' : 'false') . PHP_EOL; | ||
|
|
||
| if (!$result['ok']) { | ||
| echo 'POST failed as expected' . PHP_EOL; | ||
| } | ||
| ``` | ||
|
|
||
| **test-request-multiple:** | ||
| ```php | ||
| #!/usr/bin/env php | ||
| <?php | ||
| declare(strict_types=1); | ||
| namespace DrevOps\VortexTooling; | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| echo 'Script will call request functions multiple times' . PHP_EOL; | ||
|
|
||
| $result1 = request_get('https://example.com/first'); | ||
| echo 'First call status: ' . $result1['status'] . PHP_EOL; | ||
|
|
||
| $result2 = request_post('https://example.com/second', 'data'); | ||
| echo 'Second call status: ' . $result2['status'] . PHP_EOL; | ||
|
|
||
| $result3 = request('https://example.com/third', ['method' => 'PUT']); | ||
| echo 'Third call status: ' . $result3['status'] . PHP_EOL; | ||
|
|
||
| echo 'Script completed' . PHP_EOL; | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Align markdown with markdownlint (MD029, MD031, MD034)
The plan content looks good; the remaining issues are purely formatting and are causing markdownlint failures:
- Surround fenced code blocks with blank lines (MD031).
- Wrap bare URLs in angle brackets (e.g.,
https://example.com→<https://example.com>) (MD034). - For the “Script Tests (12 tests)” section, restart numbering at 1–12 rather than 15–26 so it matches the
1/2/3style (MD029), relying on the section title to distinguish from the direct tests.
Example adjustments:
-**Mock response structure:**
-```php
+**Mock response structure:**
+
+```php
@@
-```
+```
@@
-15. **testMockRequestGetScriptPassingSuccess** - GET through script (uses test-request-get-passing)
-16. **testMockRequestGetScriptFailingSuccess** - GET 404 through script (uses test-request-get-failing)
+1. **testMockRequestGetScriptPassingSuccess** - GET through script (uses test-request-get-passing)
+2. **testMockRequestGetScriptFailingSuccess** - GET 404 through script (uses test-request-get-failing)
@@
-- - Mock: url="https://example.com"
-- - Call: url="https://different.com"
+- - Mock: url="<https://example.com>"
+- - Call: url="<https://different.com>"Apply the same blank‑line and URL tweaks around the other fenced blocks and bullet descriptions to clear the remaining lint errors.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
11-11: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
86-86: Bare URL used
(MD034, no-bare-urls)
87-87: Bare URL used
(MD034, no-bare-urls)
92-92: Ordered list item prefix
Expected: 1; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
93-93: Ordered list item prefix
Expected: 2; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
94-94: Ordered list item prefix
Expected: 3; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
95-95: Ordered list item prefix
Expected: 4; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
96-96: Ordered list item prefix
Expected: 5; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
97-97: Ordered list item prefix
Expected: 6; Actual: 20; Style: 1/2/3
(MD029, ol-prefix)
98-98: Ordered list item prefix
Expected: 7; Actual: 21; Style: 1/2/3
(MD029, ol-prefix)
99-99: Ordered list item prefix
Expected: 8; Actual: 22; Style: 1/2/3
(MD029, ol-prefix)
100-100: Ordered list item prefix
Expected: 9; Actual: 23; Style: 1/2/3
(MD029, ol-prefix)
101-101: Ordered list item prefix
Expected: 10; Actual: 24; Style: 1/2/3
(MD029, ol-prefix)
102-102: Ordered list item prefix
Expected: 11; Actual: 25; Style: 1/2/3
(MD029, ol-prefix)
103-103: Ordered list item prefix
Expected: 12; Actual: 26; Style: 1/2/3
(MD029, ol-prefix)
113-113: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
134-134: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
176-176: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
197-197: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
| use DrevOps\VortexTooling\Tests\Unit\CoversClass; | ||
| use DrevOps\VortexTooling\Tests\Unit\UnitTestCase; | ||
|
|
||
| /** | ||
| * Self-tests for mocking of HTTP request functions. | ||
| * | ||
| * We test mockRequest() to ensure it returns the correct responses | ||
| * and validates URL/method expectations. | ||
| */ | ||
| #[CoversClass(UnitTestCase::class)] | ||
| class MockRequestSelfTest extends UnitTestCase { | ||
|
|
||
| /** | ||
| * {@inheritdoc} | ||
| */ | ||
| protected function setUp(): void { | ||
| parent::setUp(); | ||
| // Load helpers.php to make request_get/request_post/request available. | ||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
| } | ||
|
|
||
| public function testMockRequestGetSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => 'success response', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(200, $result['status']); | ||
| $this->assertEquals('success response', $result['body']); | ||
| $this->assertNull($result['error']); | ||
| } | ||
|
|
||
| public function testMockRequestGetCustomSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'ok' => TRUE, | ||
| 'status' => 201, | ||
| 'body' => 'custom body content', | ||
| 'error' => NULL, | ||
| 'info' => ['http_code' => 201, 'total_time' => 1.5], | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(201, $result['status']); | ||
| $this->assertEquals('custom body content', $result['body']); | ||
| $this->assertNull($result['error']); | ||
| $this->assertEquals(201, $result['info']['http_code']); | ||
| $this->assertEquals(1.5, $result['info']['total_time']); | ||
| } | ||
|
|
||
| public function testMockRequestGetDefaultsSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(200, $result['status']); | ||
| $this->assertEquals('', $result['body']); | ||
| $this->assertNull($result['error']); | ||
| } | ||
|
|
||
| public function testMockRequestGetFailure404(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/not-found', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 404, | ||
| 'body' => 'Not Found', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/not-found'); | ||
|
|
||
| $this->assertFalse($result['ok']); | ||
| $this->assertEquals(404, $result['status']); | ||
| $this->assertEquals('Not Found', $result['body']); | ||
| } | ||
|
|
||
| public function testMockRequestGetFailure500(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/error', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 500, | ||
| 'body' => 'Internal Server Error', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/error'); | ||
|
|
||
| $this->assertFalse($result['ok']); | ||
| $this->assertEquals(500, $result['status']); | ||
| $this->assertEquals('Internal Server Error', $result['body']); | ||
| } | ||
|
|
||
| public function testMockRequestGetNetworkError(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/timeout', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 0, | ||
| 'body' => FALSE, | ||
| 'error' => 'Could not resolve host', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/timeout'); | ||
|
|
||
| $this->assertFalse($result['ok']); | ||
| $this->assertEquals(0, $result['status']); | ||
| $this->assertFalse($result['body']); | ||
| $this->assertEquals('Could not resolve host', $result['error']); | ||
| } | ||
|
|
||
| public function testMockRequestPostSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'method' => 'POST', | ||
| 'response' => [ | ||
| 'status' => 201, | ||
| 'body' => 'created', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_post('https://example.com/api', '{"key":"value"}'); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(201, $result['status']); | ||
| $this->assertEquals('created', $result['body']); | ||
| } | ||
|
|
||
| public function testMockRequestPostJsonSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api/json', | ||
| 'method' => 'POST', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => '{"success":true}', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_post( | ||
| 'https://example.com/api/json', | ||
| json_encode(['data' => 'value']), | ||
| ['Content-Type: application/json'] | ||
| ); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(200, $result['status']); | ||
| $this->assertEquals('{"success":true}', $result['body']); | ||
| } | ||
|
|
||
| public function testMockRequestCustomMethodSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/resource', | ||
| 'method' => 'PUT', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => 'updated', | ||
| ], | ||
| ]); | ||
|
|
||
| $result = \DrevOps\VortexTooling\request('https://example.com/resource', ['method' => 'PUT']); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(200, $result['status']); | ||
| $this->assertEquals('updated', $result['body']); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => 'first response', | ||
| ], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/second', | ||
| 'method' => 'POST', | ||
| 'response' => [ | ||
| 'status' => 201, | ||
| 'body' => 'second response', | ||
| ], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/third', | ||
| 'method' => 'PUT', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => 'third response', | ||
| ], | ||
| ]); | ||
|
|
||
| $result1 = \DrevOps\VortexTooling\request_get('https://example.com/first'); | ||
| $this->assertEquals(200, $result1['status']); | ||
| $this->assertEquals('first response', $result1['body']); | ||
|
|
||
| $result2 = \DrevOps\VortexTooling\request_post('https://example.com/second', 'data'); | ||
| $this->assertEquals(201, $result2['status']); | ||
| $this->assertEquals('second response', $result2['body']); | ||
|
|
||
| $result3 = \DrevOps\VortexTooling\request('https://example.com/third', ['method' => 'PUT']); | ||
| $this->assertEquals(200, $result3['status']); | ||
| $this->assertEquals('third response', $result3['body']); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleMoreCallsFailure(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => ['status' => 200, 'body' => 'first'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/second', | ||
| 'response' => ['status' => 200, 'body' => 'second'], | ||
| ]); | ||
|
|
||
| \DrevOps\VortexTooling\request_get('https://example.com/first'); | ||
| \DrevOps\VortexTooling\request_get('https://example.com/second'); | ||
|
|
||
| $this->expectException(\RuntimeException::class); | ||
| $this->expectExceptionMessage('curl_init() called more times than mocked responses. Expected 2 request(s), but attempting request #3'); | ||
|
|
||
| \DrevOps\VortexTooling\request_get('https://example.com/third'); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleLessCallsFailure(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => ['status' => 200, 'body' => 'first'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/second', | ||
| 'response' => ['status' => 200, 'body' => 'second'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/third', | ||
| 'response' => ['status' => 200, 'body' => 'third'], | ||
| ]); | ||
|
|
||
| \DrevOps\VortexTooling\request_get('https://example.com/first'); | ||
| \DrevOps\VortexTooling\request_get('https://example.com/second'); | ||
|
|
||
| $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); | ||
| $this->expectExceptionMessage('Not all mocked request responses were consumed. Expected 3 request(s), but only 2 request(s) were made.'); | ||
|
|
||
| // Manually trigger the check that normally happens in tearDown(). | ||
| $this->mockRequestAssertAllMocksConsumed(); | ||
| } | ||
|
|
||
| public function testMockRequestFailureArgumentExceptionUrl(): void { | ||
| $this->mockRequest([ | ||
| 'response' => ['status' => 200], | ||
| ]); | ||
|
|
||
| $this->expectException(\InvalidArgumentException::class); | ||
| $this->expectExceptionMessage('Mocked request response must include "url" key to specify expected URL.'); | ||
|
|
||
| \DrevOps\VortexTooling\request_get('https://example.com/api'); | ||
| } | ||
|
|
||
| public function testMockRequestFailureAssertUnexpectedUrl(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/expected', | ||
| 'response' => ['status' => 200], | ||
| ]); | ||
|
|
||
| $this->expectException(\RuntimeException::class); | ||
| $this->expectExceptionMessage('request made to unexpected URL. Expected "https://example.com/expected", got "https://example.com/actual".'); | ||
|
|
||
| \DrevOps\VortexTooling\request_get('https://example.com/actual'); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptPassingSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| 'body' => 'success response', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-get-passing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Script will call request_get', $output); | ||
| $this->assertStringContainsString('Response status: 200', $output); | ||
| $this->assertStringContainsString('Response ok: true', $output); | ||
| $this->assertStringContainsString('Response body: success response', $output); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptFailingSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/not-found', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 404, | ||
| 'body' => 'Not Found', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-get-failing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Script will call request_get expecting failure', $output); | ||
| $this->assertStringContainsString('Response status: 404', $output); | ||
| $this->assertStringContainsString('Response ok: false', $output); | ||
| $this->assertStringContainsString('Request failed as expected', $output); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptCustomSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'status' => 201, | ||
| 'body' => 'custom response', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-get-passing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Response status: 201', $output); | ||
| $this->assertStringContainsString('Response body: custom response', $output); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptDefaultsSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'response' => [ | ||
| 'status' => 200, | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-get-passing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Response status: 200', $output); | ||
| $this->assertStringContainsString('Response ok: true', $output); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptNetworkErrorSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/not-found', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 0, | ||
| 'body' => FALSE, | ||
| 'error' => 'Could not resolve host', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-get-failing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Response status: 0', $output); | ||
| $this->assertStringContainsString('Response ok: false', $output); | ||
| } | ||
|
|
||
| public function testMockRequestPostScriptPassingSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/api', | ||
| 'method' => 'POST', | ||
| 'response' => [ | ||
| 'status' => 201, | ||
| 'body' => 'created', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-post-passing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Script will call request_post', $output); | ||
| $this->assertStringContainsString('Response status: 201', $output); | ||
| $this->assertStringContainsString('Response ok: true', $output); | ||
| $this->assertStringContainsString('POST succeeded', $output); | ||
| } | ||
|
|
||
| public function testMockRequestPostScriptFailingSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/error', | ||
| 'method' => 'POST', | ||
| 'response' => [ | ||
| 'ok' => FALSE, | ||
| 'status' => 500, | ||
| 'body' => 'Internal Server Error', | ||
| ], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-post-failing', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Script will call request_post expecting failure', $output); | ||
| $this->assertStringContainsString('Response status: 500', $output); | ||
| $this->assertStringContainsString('Response ok: false', $output); | ||
| $this->assertStringContainsString('POST failed as expected', $output); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleScriptSuccess(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => ['status' => 200, 'body' => 'first'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/second', | ||
| 'method' => 'POST', | ||
| 'response' => ['status' => 201, 'body' => 'second'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/third', | ||
| 'method' => 'PUT', | ||
| 'response' => ['status' => 200, 'body' => 'third'], | ||
| ]); | ||
|
|
||
| $output = $this->runScript('test-request-multiple', 'tests/Fixtures'); | ||
|
|
||
| $this->assertStringContainsString('Script will call request functions multiple times', $output); | ||
| $this->assertStringContainsString('First call status: 200', $output); | ||
| $this->assertStringContainsString('Second call status: 201', $output); | ||
| $this->assertStringContainsString('Third call status: 200', $output); | ||
| $this->assertStringContainsString('Script completed', $output); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleScriptMoreCallsFailure(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => ['status' => 200, 'body' => 'first'], | ||
| ]); | ||
|
|
||
| $this->expectException(\RuntimeException::class); | ||
| $this->expectExceptionMessage('curl_init() called more times than mocked responses. Expected 1 request(s), but attempting request #2'); | ||
|
|
||
| $this->runScript('test-request-multiple', 'tests/Fixtures'); | ||
| } | ||
|
|
||
| public function testMockRequestMultipleScriptLessCallsFailure(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/first', | ||
| 'response' => ['status' => 200, 'body' => 'first'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/second', | ||
| 'method' => 'POST', | ||
| 'response' => ['status' => 201, 'body' => 'second'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/third', | ||
| 'method' => 'PUT', | ||
| 'response' => ['status' => 200, 'body' => 'third'], | ||
| ]); | ||
|
|
||
| $this->mockRequest([ | ||
| 'url' => 'https://example.com/fourth', | ||
| 'response' => ['status' => 200, 'body' => 'fourth'], | ||
| ]); | ||
|
|
||
| $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); | ||
| $this->expectExceptionMessage('Not all mocked request responses were consumed. Expected 4 request(s), but only 3 request(s) were made.'); | ||
|
|
||
| $this->runScript('test-request-multiple', 'tests/Fixtures'); | ||
|
|
||
| // Manually trigger the check that normally happens in tearDown(). | ||
| $this->mockRequestAssertAllMocksConsumed(); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptFailureArgumentExceptionUrl(): void { | ||
| $this->mockRequest([ | ||
| 'response' => ['status' => 200], | ||
| ]); | ||
|
|
||
| $this->expectException(\InvalidArgumentException::class); | ||
| $this->expectExceptionMessage('Mocked request response must include "url" key to specify expected URL.'); | ||
|
|
||
| $this->runScript('test-request-get-passing', 'tests/Fixtures'); | ||
| } | ||
|
|
||
| public function testMockRequestGetScriptFailureAssertUnexpectedUrl(): void { | ||
| $this->mockRequest([ | ||
| 'url' => 'https://wrong.com/api', | ||
| 'response' => ['status' => 200], | ||
| ]); | ||
|
|
||
| $this->expectException(\RuntimeException::class); | ||
| $this->expectExceptionMessage('request made to unexpected URL. Expected "https://wrong.com/api", got "https://example.com/api".'); | ||
|
|
||
| $this->runScript('test-request-get-passing', 'tests/Fixtures'); | ||
| } | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import functions/classes instead of using fully‑qualified names in the body
PHPCS is flagging all \DrevOps\VortexTooling\request_* and \RuntimeException, \InvalidArgumentException, \PHPUnit\Framework\AssertionFailedError usages. Bringing these in via use/use function will fix the pipeline and keep the tests cleaner.
Suggested patch:
@@
-namespace DrevOps\VortexTooling\Tests\Self;
-
-use DrevOps\VortexTooling\Tests\Unit\CoversClass;
-use DrevOps\VortexTooling\Tests\Unit\UnitTestCase;
+namespace DrevOps\VortexTooling\Tests\Self;
+
+use DrevOps\VortexTooling\Tests\Unit\CoversClass;
+use DrevOps\VortexTooling\Tests\Unit\UnitTestCase;
+use InvalidArgumentException;
+use PHPUnit\Framework\AssertionFailedError;
+use RuntimeException;
+use function DrevOps\VortexTooling\request;
+use function DrevOps\VortexTooling\request_get;
+use function DrevOps\VortexTooling\request_post;
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/api');
+ $result = request_get('https://example.com/api');
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/api');
+ $result = request_get('https://example.com/api');
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/api');
+ $result = request_get('https://example.com/api');
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/not-found');
+ $result = request_get('https://example.com/not-found');
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/error');
+ $result = request_get('https://example.com/error');
@@
- $result = \DrevOps\VortexTooling\request_get('https://example.com/timeout');
+ $result = request_get('https://example.com/timeout');
@@
- $result = \DrevOps\VortexTooling\request_post('https://example.com/api', '{"key":"value"}');
+ $result = request_post('https://example.com/api', '{"key":"value"}');
@@
- $result = \DrevOps\VortexTooling\request_post(
+ $result = request_post(
@@
- $result = \DrevOps\VortexTooling\request('https://example.com/resource', ['method' => 'PUT']);
+ $result = request('https://example.com/resource', ['method' => 'PUT']);
@@
- $result1 = \DrevOps\VortexTooling\request_get('https://example.com/first');
+ $result1 = request_get('https://example.com/first');
@@
- $result2 = \DrevOps\VortexTooling\request_post('https://example.com/second', 'data');
+ $result2 = request_post('https://example.com/second', 'data');
@@
- $result3 = \DrevOps\VortexTooling\request('https://example.com/third', ['method' => 'PUT']);
+ $result3 = request('https://example.com/third', ['method' => 'PUT']);
@@
- \DrevOps\VortexTooling\request_get('https://example.com/first');
- \DrevOps\VortexTooling\request_get('https://example.com/second');
+ request_get('https://example.com/first');
+ request_get('https://example.com/second');
@@
- \DrevOps\VortexTooling\request_get('https://example.com/third');
+ request_get('https://example.com/third');
@@
- \DrevOps\VortexTooling\request_get('https://example.com/first');
- \DrevOps\VortexTooling\request_get('https://example.com/second');
+ request_get('https://example.com/first');
+ request_get('https://example.com/second');
@@
- \DrevOps\VortexTooling\request_get('https://example.com/api');
+ request_get('https://example.com/api');
@@
- \DrevOps\VortexTooling\request_get('https://example.com/actual');
+ request_get('https://example.com/actual');
@@
- $this->expectException(\RuntimeException::class);
+ $this->expectException(RuntimeException::class);
@@
- $this->expectException(\PHPUnit\Framework\AssertionFailedError::class);
+ $this->expectException(AssertionFailedError::class);
@@
- $this->expectException(\InvalidArgumentException::class);
+ $this->expectException(InvalidArgumentException::class);
@@
- $this->expectException(\RuntimeException::class);
+ $this->expectException(RuntimeException::class);(You can similarly replace the remaining \DrevOps\VortexTooling\request_* invocations, if any, with the imported functions.)
The rest of the test logic and coverage look solid once standards are satisfied.
🧰 Tools
🪛 GitHub Actions: Vortex - Test tooling
[error] 37-37: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 57-57: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 75-75: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 93-93: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 110-110: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 128-128: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 146-146: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 163-163: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 184-184: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 218-218: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 222-222: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 226-226: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 242-242: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 243-243: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 248-248: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 267-267: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 268-268: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 270-270: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 285-285: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 297-297: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
[error] 480-480: PHPCS: Namespaced classes/interfaces/traits should be referenced with use statements (Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing)
🤖 Prompt for AI Agents
.vortex/tooling/tests/Self/MockRequestSelfTest.php lines 7-512: PHPCS flags
full-qualified names for the request helpers and exception classes; import them
at the top and use short names in the test body. Add appropriate use statements
(e.g. use function DrevOps\VortexTooling\request_get; use function
DrevOps\VortexTooling\request_post; use function DrevOps\VortexTooling\request;
and use RuntimeException; use InvalidArgumentException; use
PHPUnit\Framework\AssertionFailedError;) and then replace all occurrences of the
leading-backslash qualified names in the file with the imported short names
(request_get, request_post, request, RuntimeException, InvalidArgumentException,
AssertionFailedError).
9b2f100 to
daaa5df
Compare
daaa5df to
ad38200
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.vortex/CLAUDE.md (1)
759-766: Update “three systems” summary to include toolingThis section still describes
.vortex/as having “three independent systems” (docs, installer, tests), but earlier sections now document four subsystems (docs, installer, tooling, tests) and a separate Tooling Tests subsystem.Consider updating this summary block to stay aligned, for example:
-### Test Harness (.vortex/) - Three Separate Systems +### Test Harness (.vortex/) - Four Independent Systems @@ -**Critical Understanding**: The `.vortex/` directory contains three **completely independent subsystems**: +**Critical Understanding**: The `.vortex/` directory contains four **completely independent subsystems**: @@ -1. **`.vortex/docs/`** - Docusaurus website (Node.js/React) -2. **`.vortex/installer/`** - PHP installer application (Symfony Console) -3. **`.vortex/tests/`** - Template testing harness (PHPUnit + BATS) +1. **`.vortex/docs/`** - Docusaurus website (Node.js/React) +2. **`.vortex/installer/`** - PHP installer application (Symfony Console) +3. **`.vortex/tooling/`** - PHP helpers and notification tooling (with its own tests) +4. **`.vortex/tests/`** - Template testing harness (PHPUnit + BATS + manual)This keeps the high-level count consistent with the rest of the document.
♻️ Duplicate comments (20)
.vortex/tooling/rector.php (2)
38-48: Includesrcdirectory to ensurehelpers.phpis processed by Rector.The current paths list specific
src/notify*subdirectories but excludesrc/helpers.php, which exists and should be modernized by Rector. Update to include the entiresrcdirectory.Apply this diff:
->withPaths([ - __DIR__ . '/src/notify', - __DIR__ . '/src/notify-email', - __DIR__ . '/src/notify-github', - __DIR__ . '/src/notify-jira', - __DIR__ . '/src/notify-newrelic', - __DIR__ . '/src/notify-slack', - __DIR__ . '/src/notify-webhook', + __DIR__ . '/src', __DIR__ . '/tests', ])
75-77: Align PHP version comments with configuration.The comments state "modernizes syntax to PHP 8.3" and "rules from PHP 5.3 through 8.3", but the configuration uses
php82: TRUE. Either update the comments to reflect PHP 8.2 or change the configuration tophp83: TRUEto match the comments.Apply this diff to match comments (if PHP 8.3 is intended):
- ->withPhpSets(php82: TRUE) + ->withPhpSets(php83: TRUE)Or update comments to reflect PHP 8.2:
- // PHP version upgrade sets - modernizes syntax to PHP 8.3. - // Includes all rules from PHP 5.3 through 8.3. + // PHP version upgrade sets - modernizes syntax to PHP 8.2. + // Includes all rules from PHP 5.3 through 8.2. ->withPhpSets(php82: TRUE).github/workflows/vortex-test-tooling.yml (1)
53-57: ClarifyGITHUB_TOKENvsPACKAGE_TOKENmappingThis step exposes
GITHUB_TOKENwhile sourcing it fromsecrets.PACKAGE_TOKEN. That’s easy to misread as the standard GitHub token rather than a higher-privilege PAT.Either rename the env var or add a short comment explaining why
PACKAGE_TOKENis mapped here.- name: Run tests run: composer test-coverage working-directory: .vortex/tooling env: - GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + # If tooling requires a PAT, use PACKAGE_TOKEN instead of the default GITHUB_TOKEN.Or, if it really should be the default token, switch to
secrets.GITHUB_TOKEN..vortex/tooling/test_plan.md (1)
10-26: Fix remaining markdownlint issues (fences, URLs, numbering)markdownlint is still flagging this file for:
- Fenced blocks not surrounded by blank lines (MD031).
- Bare URLs (MD034).
- Script Tests list numbered 15–26 instead of 1–12 (MD029).
You can address these with small structural tweaks, e.g.:
-**Mock response structure:** -```php +**Mock response structure:** + +```php @@ -``` +``` + @@ -15. **testMockRequestGetScriptPassingSuccess** - GET through script (uses test-request-get-passing) -16. **testMockRequestGetScriptFailingSuccess** - GET 404 through script (uses test-request-get-failing) +1. **testMockRequestGetScriptPassingSuccess** - GET through script (uses test-request-get-passing) +2. **testMockRequestGetScriptFailingSuccess** - GET 404 through script (uses test-request-get-failing) @@ -- - Mock: url="https://example.com" -- - Call: url="https://different.com" +- - Mock: url="<https://example.com>" +- - Call: url="<https://different.com>"Apply the same “blank line around fences” and “wrap URLs in
<...>” pattern to the other sections to clear the remaining lint errors.Also applies to: 90-103, 110-217
.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
1-499: Test coverage is comprehensive and well-structured.The self-test suite thoroughly validates HTTP request mocking across success, failure, custom values, defaults, network errors, multiple sequential calls, and script-based validations. The test design follows established patterns from MockPassthruSelfTest and MockQuitSelfTest.
.vortex/tooling/src/notify-newrelic (1)
1-176: Script structure and notification flow are well-designed.The New Relic notification script properly handles environment variables, auto-generates revision when needed, discovers app IDs by name, validates configuration, and posts deployment notifications with appropriate error handling for the API response.
.vortex/tooling/src/notify-github (2)
61-71: Relax deployment ID length upper bound to future‑proof validation
validate_deployment_id()currently requires 9–11 digits:// Check length (9-11 digits) and numeric format. return $length >= 9 && $length <= 11 && ctype_digit($id_str);This hard upper bound risks rejecting valid deployment IDs if GitHub increases their size. You can keep the numeric and minimum‑length checks while dropping the upper bound, e.g.:
- // Check length (9-11 digits) and numeric format. - return $length >= 9 && $length <= 11 && ctype_digit($id_str); + // Check length (at least 9 digits) and numeric format. + return $length >= 9 && ctype_digit($id_str);
88-92: Explicitly mark JSON POSTs withContent-Type: application/jsonBoth deployment creation and status update send JSON bodies but don’t set a JSON content type. Making this explicit avoids relying on any
request_post()defaults and lines up with GitHub’s expectations for JSON requests.Suggested change:
$response = request_post($url, json_encode($payload_data), [ - 'Authorization: token ' . $token, - 'Accept: application/vnd.github.v3+json', + 'Authorization: token ' . $token, + 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]); @@ $response = request_post($url, json_encode($status_data), [ - 'Accept: application/vnd.github.v3+json', - 'Authorization: token ' . $token, + 'Accept: application/vnd.github.v3+json', + 'Authorization: token ' . $token, + 'Content-Type: application/json', ]);Also applies to: 146-150
.vortex/tooling/src/notify-email (1)
117-151: Filter out empty recipients after explode/trim
$recipientsis currently:$recipients = array_map(trim(...), explode(',', $recipients));If the env var contains extra commas or whitespace, this yields empty strings, which then produce blank
To:headers and unnecessarysendmail/Filter out empty entries before looping:
-// Parse and send to each recipient. -$sent = []; -$recipients = array_map(trim(...), explode(',', $recipients)); +// Parse and send to each recipient. +$sent = []; +$recipients = array_filter( + array_map(trim(...), explode(',', $recipients)), + static fn(string $value): bool => $value !== '' +);.vortex/tooling/src/notify-slack (1)
137-144: Also validate Slack webhook response body, not just HTTP 200Currently you only fail when
status !== 200:$response = request_post($webhook, $payload, [ 'Content-Type: application/json', ]); if ($response['status'] !== 200) { fail('Unable to send notification to Slack. HTTP status: %s', $response['status']); }Slack can respond with HTTP 200 but an error string in the body. To catch misconfigurations and invalid payloads, consider also checking the body, e.g.:
$response = request_post($webhook, $payload, [ 'Content-Type: application/json', ]); -$if ($response['status'] !== 200) { - fail('Unable to send notification to Slack. HTTP status: %s', $response['status']); -} +$body = trim((string) ($response['body'] ?? '')); +if ($response['status'] !== 200 || ($body !== '' && $body !== 'ok')) { + fail( + 'Unable to send notification to Slack. HTTP status: %s, body: %s', + $response['status'], + $body !== '' ? $body : '<empty>' + ); +}.vortex/tooling/src/notify-jira (3)
91-112: Fix issue extraction: useextract_issue()and exit when label has no issue
extract_issue()is defined but not used, and the inlinepreg_matchblock doesn’t exit when no issue is found:task('Extracting issue'); if (!preg_match('/([^\/]+\/)?([A-Za-z0-9]+\-\d+)/', $label, $matches)) { pass('Deployment label %s does not contain issue number.', $label); } $issue = $matches[2]; note('Found issue %s.', $issue);If the label lacks an issue key,
$matchesand$issueare undefined yet the script continues, leading to runtime notices and broken requests later.Leverage the helper and bail out cleanly:
task('Extracting issue'); -if (!preg_match('/([^\/]+\/)?([A-Za-z0-9]+\-\d+)/', $label, $matches)) { - pass('Deployment label %s does not contain issue number.', $label); -} -$issue = $matches[2]; -note('Found issue %s.', $issue); +$issue = extract_issue($label); +if ($issue === NULL) { + pass('Deployment label %s does not contain issue number.', $label); + exit(0); +} +note('Found issue %s.', $issue);This removes duplication, uses the dedicated helper, and prevents the notifier from running with an invalid/undefined issue key.
83-90: Clean up default message template and unused$message_contentTwo related issues around the message template:
- The default heredoc has an extra double quote:
$message = $message ?: <<<EOT ## This is an automated message ##"This will render an unintended
"at the end of the first line. It should be:-$message = $message ?: <<<EOT -## This is an automated message ##" +$message = $message ?: <<<EOT +## This is an automated message ##
$message_contentis built withreplace_tokens()but never used; the ADF payload below is hard-coded, soVORTEX_NOTIFY_JIRA_MESSAGEcurrently has no effect:// Build message by replacing tokens. $message_content = replace_tokens($message, [ 'project' => $project, 'label' => $label, 'timestamp' => $timestamp, 'environment_url' => $env_url, 'login_url' => $login_url, ]);Either wire
$message_contentinto the ADF structure (so custom templates are honored) or remove this block and the related env var from the docs to avoid dead code and misleading configuration.Also applies to: 140-151
130-135: Replace rawechocalls with standard logging helpersThere are several
echostatements mixed into a script that otherwise usestask(),note(),pass(),fail():echo "success\n"; echo ' Discovering transition ID for ' . $transition . '...'; echo "success\n"; echo ' Discovering user ID for ' . $assignee_email . '...'; echo "success\n";For consistent output formatting and easier parsing, prefer the existing helpers, e.g.:
-if (strlen((string) $account_id) < 24) { - fail('Unable to authenticate'); -} -echo "success\n"; +if (strlen((string) $account_id) < 24) { + fail('Unable to authenticate'); +} +note('success'); @@ - echo ' Discovering transition ID for ' . $transition . '...'; + note('Discovering transition ID for ' . $transition . '...'); @@ - echo "success\n"; + note('success'); @@ - echo ' Discovering user ID for ' . $assignee_email . '...'; + note('Discovering user ID for ' . $assignee_email . '...'); @@ - echo "success\n"; + note('success');Also applies to: 212-213, 235-235, 250-251, 269-269
.vortex/tooling/src/notify (1)
68-88: UseDIRECTORY_SEPARATORwhen building script paths for portabilityThe per‑channel script path is currently built with a hardcoded
/:$script = __DIR__ . ('/notify-' . $channel);For better cross‑platform compatibility (e.g., Windows paths), prefer
DIRECTORY_SEPARATOR:- $script = __DIR__ . ('/notify-' . $channel); + $script = __DIR__ . DIRECTORY_SEPARATOR . 'notify-' . $channel;.vortex/tooling/src/notify-webhook (2)
79-92: Handlejson_encode()failure injson_escape_value()to avoid TypeError
json_escape_value()callsjson_encode()and immediatelysubstr()s the result. Ifjson_encode()returnsfalse(e.g., malformed UTF‑8),substr(false, ...)will throw aTypeError. Add a strict failure check before callingsubstr().function json_escape_value(string $value): string { - $escaped = json_encode($value); - // Remove surrounding quotes. - return substr($escaped, 1, -1); + $escaped = json_encode($value); + if ($escaped === FALSE) { + fail('Failed to JSON-encode value for webhook payload: %s (error: %s)', $value, json_last_error_msg()); + } + // Remove surrounding quotes. + return substr($escaped, 1, -1); }
112-138: Avoid logging full webhook URL; use masked domain in error messageOn failure you log the full
$webhook_url, which may embed secrets (tokens in path/query). You already compute$webhook_domainfor masking; reuse it in the error.$webhook_domain = preg_replace('|(https?://[^/]+).*|', '$1', $webhook_url); @@ -if ($response['status'] !== (int) $expected_status) { - fail(sprintf('Unable to send notification to webhook %s.', $webhook_url)); -} +if ($response['status'] !== (int) $expected_status) { + fail(sprintf('Unable to send notification to webhook %s/***.', $webhook_domain)); +}.vortex/tooling/tests/Traits/MockTrait.php (1)
83-96: Align passthru mock docblock with actual response keysThe docblock still references
exit_code, but the structure and implementation useresult_codepluscmd/return. This can confuse consumers ofmockPassthruMultiple().- * @param array<int, array{cmd:string, output?: string, result_code?: int, return?: NULL|FALSE}> $responses - * Array of responses to return for each passthru call. - * Each response should have: - * - output: The output to echo - * - exit_code: The exit code to set (0 for success). + * @param array<int, array{cmd:string, output?: string, result_code?: int, return?: NULL|FALSE}> $responses + * Array of responses to return for each passthru call. + * Each response should have: + * - cmd: Expected command (required) + * - output: Output to echo (optional) + * - result_code: Exit code to set (optional, default 0) + * - return: Return value (optional, NULL or FALSE).vortex/tooling/src/helpers.php (3)
22-31: Hardenexecute_override()against path traversal in$name
$nameis concatenated directly into$dir . DIRECTORY_SEPARATOR . $name. While current callers passbasename(__FILE__), future usage with untrusted input could enable path traversal. Validate that$namehas no directory separators before building the path.function execute_override(string $name): void { + // Prevent path traversal by ensuring name doesn't contain directory separators. + if (str_contains($name, DIRECTORY_SEPARATOR) || str_contains($name, '/') || str_contains($name, '\\')) { + fail('Invalid script name for override: %s', $name); + } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); if ($dir) { $path = $dir . DIRECTORY_SEPARATOR . $name;
294-298: Remove unreachableexit(1)afterfail()inrequest()
fail()already prints the error and callsexit(1), so the subsequentexit(1)is dead code.function request(string $url, array $options = []): array { if (!function_exists('curl_init')) { fail('curl extension is not available.'); - exit(1); }
300-301: Checkcurl_init()return value before using the handle
curl_init($url)can returnfalseon failure. Passingfalseintocurl_setopt_array()will cause aTypeError. Add a strict check and fail early.- $ch = curl_init($url); + $ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (36)
.github/workflows/vortex-test-tooling.yml(1 hunks).vortex/CLAUDE.md(9 hunks).vortex/tooling/.gitignore(1 hunks).vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/phpstan.neon(1 hunks).vortex/tooling/phpunit.xml(1 hunks).vortex/tooling/rector.php(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/test_plan.md(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-failing(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-passing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-failing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-passing(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Traits/MockTrait.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/tests/Fixtures/test-request-post-passing.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/phpunit.xml.vortex/tooling/tests/Fixtures/test-request-get-passing.vortex/tooling/test_plan.md.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/CLAUDE.md.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Fixtures/test-request-multiple
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/tests/Fixtures/test-quit-failing
📚 Learning: 2025-06-02T00:41:36.321Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1697
File: .vortex/installer/tests/Fixtures/install/provision_database_lagoon/docs/onboarding.md:16-17
Timestamp: 2025-06-02T00:41:36.321Z
Learning: DO NOT EVER REVIEW FILES IN .vortex/installer/tests/Fixtures/ directory - these are test fixtures and should not be reviewed under any circumstances.
Applied to files:
.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/CLAUDE.md
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.
Applied to files:
.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/.gitignore.vortex/CLAUDE.md
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/CLAUDE.md.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Fixtures/test-passthru-passing
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.github/workflows/vortex-test-tooling.yml
📚 Learning: 2025-06-03T03:15:29.849Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1714
File: scripts/vortex/doctor.sh:69-70
Timestamp: 2025-06-03T03:15:29.849Z
Learning: In the Vortex project, logging functions (task(), info(), note(), pass(), fail(), warn()) are intentionally duplicated across all script files to make each script self-contained and independent, rather than sharing them through a common library. This design choice prioritizes script independence over reducing code duplication.
Applied to files:
.vortex/CLAUDE.md
🧬 Code graph analysis (8)
.vortex/tooling/tests/Self/MockQuitSelfTest.php (5)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (1)
quit(369-371)
.vortex/tooling/tests/Unit/UnitTestCase.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (1)
mockTearDown(63-81)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (4)
mockRequestGet(401-414)mockRequestPost(432-446)mockRequest(369-385)mockRequestAssertAllMocksConsumed(454-465).vortex/tooling/src/helpers.php (3)
request_get(237-243)request_post(265-272)request(294-349)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
__construct(20-26)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
__construct(20-26)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
.vortex/tooling/tests/Traits/MockTrait.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/src/helpers.php (2)
fail(149-152)quit(369-371)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~140-~140: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/scripts_conversion_plan.md
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
17-17: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
43-43: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
48-48: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
59-59: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
68-68: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
92-92: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
93-93: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
103-103: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
112-112: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
126-126: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
139-139: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
140-140: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
146-146: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
147-147: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
167-167: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
182-182: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
278-278: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
283-283: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
386-386: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
393-393: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
394-394: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
395-395: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
396-396: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
397-397: Ordered list item prefix
Expected: 4; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
398-398: Ordered list item prefix
Expected: 5; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
399-399: Ordered list item prefix
Expected: 6; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
401-401: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
402-402: Ordered list item prefix
Expected: 1; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
403-403: Ordered list item prefix
Expected: 2; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
404-404: Ordered list item prefix
Expected: 3; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
405-405: Ordered list item prefix
Expected: 4; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
406-406: Ordered list item prefix
Expected: 5; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
431-431: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
432-432: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
440-440: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
441-441: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
453-453: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
454-454: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
466-466: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
467-467: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
475-475: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
476-476: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
489-489: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
496-496: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
503-503: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
.vortex/tooling/CLAUDE.md
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
83-83: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
128-128: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
143-143: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
153-153: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
171-171: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
216-216: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
310-310: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
317-317: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
322-322: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
390-390: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
422-422: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
437-437: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
438-438: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
447-447: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
448-448: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
455-455: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
483-483: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
484-484: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
497-497: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
502-502: Ordered list item prefix
Expected: 1; Actual: 4; Style: 1/1/1
(MD029, ol-prefix)
646-646: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
727-727: Bare URL used
(MD034, no-bare-urls)
728-728: Bare URL used
(MD034, no-bare-urls)
729-729: Bare URL used
(MD034, no-bare-urls)
.vortex/tooling/test_plan.md
11-11: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
86-86: Bare URL used
(MD034, no-bare-urls)
87-87: Bare URL used
(MD034, no-bare-urls)
92-92: Ordered list item prefix
Expected: 1; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
93-93: Ordered list item prefix
Expected: 2; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
94-94: Ordered list item prefix
Expected: 3; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
95-95: Ordered list item prefix
Expected: 4; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
96-96: Ordered list item prefix
Expected: 5; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
97-97: Ordered list item prefix
Expected: 6; Actual: 20; Style: 1/2/3
(MD029, ol-prefix)
98-98: Ordered list item prefix
Expected: 7; Actual: 21; Style: 1/2/3
(MD029, ol-prefix)
99-99: Ordered list item prefix
Expected: 8; Actual: 22; Style: 1/2/3
(MD029, ol-prefix)
100-100: Ordered list item prefix
Expected: 9; Actual: 23; Style: 1/2/3
(MD029, ol-prefix)
101-101: Ordered list item prefix
Expected: 10; Actual: 24; Style: 1/2/3
(MD029, ol-prefix)
102-102: Ordered list item prefix
Expected: 11; Actual: 25; Style: 1/2/3
(MD029, ol-prefix)
103-103: Ordered list item prefix
Expected: 12; Actual: 26; Style: 1/2/3
(MD029, ol-prefix)
113-113: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
134-134: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
176-176: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
197-197: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-installer (8.3)
🔇 Additional comments (24)
.vortex/tooling/.gitignore (1)
1-3: LGTM!The gitignore entries are appropriate for a PHP library package. Ignoring
composer.lockis correct for libraries (it should only be committed for applications), and the.logsdirectory aligns with the test output configuration in phpunit.xml..vortex/tooling/tests/Fixtures/test-passthru-passing (1)
1-19: LGTM!The test fixture follows all documented conventions: shebang, strict types, proper namespace, loads helpers, and produces testable output. The logic correctly demonstrates passthru behavior for testing purposes.
.vortex/tooling/phpstan.neon (1)
1-28: LGTM!The PHPStan configuration is appropriately strict (level 9) and the ignore rules are justified:
- The iterable type ignore for tests is reasonable for data providers
- The function-not-found ignore for
tests/Self/*is appropriate since those tests exercise mocked functions that won't exist during static analysis.vortex/tooling/phpunit.xml (1)
1-42: LGTM!The PHPUnit configuration is comprehensive and correct. The schema version (12.4) now matches the PHPUnit version constraint in composer.json (^12.4.2), addressing the previous concern. The strict settings for warnings, errors, and notices ensure high test quality.
.vortex/tooling/phpcs.xml (1)
1-47: LGTM!The PHPCS configuration appropriately bases on Drupal standards with custom additions and provides sensible test-file exclusions. The explicit file listings (while potentially redundant if PHPCS scans recursively) are clear and functional. The previous nitpick about simplification remains valid but non-blocking.
.vortex/tooling/composer.json (1)
1-65: LGTM!The Composer manifest is well-structured with appropriate metadata, autoloading, and tooling scripts. The PHP constraint placement in
require-devwas previously reviewed and correctly retained for an internal tooling package..vortex/tooling/CLAUDE.md (1)
1-733: LGTM!The documentation is comprehensive and well-structured, covering package overview, helper functions, testing architecture, naming conventions, and best practices. The previous API inconsistency concern was addressed. The static analysis formatting hints (blank lines, list prefixes, bare URLs) are minor style suggestions that don't affect documentation quality.
.github/workflows/vortex-test-tooling.yml (1)
18-21: Confirm setup-php supports all matrix PHP versionsThe matrix includes
'8.5'; please confirm thatshivammathur/setup-phpsupports this version alias in your GitHub-hosted runners so the workflow doesn’t start failing when that job is selected.You can verify against the action docs / release notes for the pinned SHA.
.vortex/CLAUDE.md (1)
18-22: Tooling subsystem and testing docs look consistentThe additions for
.vortex/tooling/and “Tooling Tests” integrate cleanly with the existing docs: four subsystems under.vortex/, five testing systems, and clear delegation of all tooling-specific details to.vortex/tooling/CLAUDE.md. This keeps the main guide high-level while still discoverable.Also applies to: 30-31, 92-107, 141-143, 181-185, 569-576, 584-585, 591-592
.vortex/tooling/tests/Fixtures/test-quit-passing (1)
1-20: Treating this as a behavior fixture, not for style reviewThis fixture is syntactically valid and matches the intended “quit with code 0” behavior; given project conventions around test fixtures, I’m not reviewing it for broader code-quality concerns.
Based on learnings
.vortex/tooling/tests/Fixtures/test-request-get-failing (1)
1-24: Fixture accepted without style reviewThis negative
request_getfixture is structurally consistent and syntactically fine; per the project’s guidance on fixtures, I’m not applying additional code-quality review here.Based on learnings
.vortex/tooling/tests/Fixtures/test-request-post-failing (1)
1-24: POST failure fixture looks fine; skipping style reviewThe script correctly exercises a failing
request_postscenario and is syntactically valid. No further style or structural review applied, consistent with fixture conventions.Based on learnings
.vortex/tooling/tests/Fixtures/test-request-get-passing (1)
1-24: GET success fixture accepted as-isThis
request_getsuccess fixture matches the documented pattern and is syntactically correct; no additional code-quality review applied due to its role as a test fixture.Based on learnings
.vortex/tooling/tests/Fixtures/test-passthru-failing (1)
1-19: Passthru failure fixture OK; no further reviewThe passthru-based failure fixture is syntactically correct and aligned with the tooling test plan; not applying style/structure review per fixture best practices.
Based on learnings
.vortex/tooling/src/helpers.php
Outdated
| function validate_command(string $command): void { | ||
| exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); | ||
| if ($code !== 0) { | ||
| fail(sprintf("Command '%s' is not available", $command)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shell-escape $command in validate_command() to avoid injection risk
validate_command() interpolates $command directly into a shell string. If $command ever comes from untrusted input, this becomes a command-injection vector. Even if you currently pass only constants, escaping is cheap and safer.
function validate_command(string $command): void {
- exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code);
+ exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function validate_command(string $command): void { | |
| exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); | |
| if ($code !== 0) { | |
| fail(sprintf("Command '%s' is not available", $command)); | |
| } | |
| function validate_command(string $command): void { | |
| exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); | |
| if ($code !== 0) { | |
| fail(sprintf("Command '%s' is not available", $command)); | |
| } |
🤖 Prompt for AI Agents
.vortex/tooling/src/helpers.php around lines 175 to 179: the function
interpolates $command directly into a shell invocation which can allow command
injection; change the sprintf to quote/escape the argument before passing it to
the shell (e.g. wrap the command in escapeshellarg() or otherwise
sanitize/validate it) so the executed string becomes safe (also consider
rejecting empty/null commands); keep the same exit-code check and fail behavior.
| protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void { | ||
| // Normalize responses by applying defaults before storing. | ||
| $normalized_responses = []; | ||
| foreach ($responses as $response) { | ||
| // @phpstan-ignore-next-line nullCoalesce.offset | ||
| $response['response'] = ($response['response'] ?? []) + [ | ||
| 'ok' => TRUE, | ||
| 'status' => 200, | ||
| 'body' => '', | ||
| 'error' => NULL, | ||
| 'info' => [], | ||
| ]; | ||
| $normalized_responses[] = $response; | ||
| } | ||
|
|
||
| // Add normalized responses to the class property. | ||
| $this->mockRequestResponses = array_merge($this->mockRequestResponses, $normalized_responses); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Validate response shape in mockRequestMultiple() before applying defaults
mockRequestMultiple() assumes $response['response'] is an array when doing ($response['response'] ?? []) + [...]. If a caller accidentally passes a non-array (e.g., string), this will fatally error with unsupported operand types. A small upfront validation would give a clearer failure.
protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void {
// Normalize responses by applying defaults before storing.
$normalized_responses = [];
foreach ($responses as $response) {
- // @phpstan-ignore-next-line nullCoalesce.offset
- $response['response'] = ($response['response'] ?? []) + [
+ if (isset($response['response']) && !is_array($response['response'])) {
+ throw new \InvalidArgumentException('Mocked request response "response" key must be an array.');
+ }
+
+ // @phpstan-ignore-next-line nullCoalesce.offset
+ $response['response'] = ($response['response'] ?? []) + [
'ok' => TRUE,
'status' => 200,
'body' => '',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void { | |
| // Normalize responses by applying defaults before storing. | |
| $normalized_responses = []; | |
| foreach ($responses as $response) { | |
| // @phpstan-ignore-next-line nullCoalesce.offset | |
| $response['response'] = ($response['response'] ?? []) + [ | |
| 'ok' => TRUE, | |
| 'status' => 200, | |
| 'body' => '', | |
| 'error' => NULL, | |
| 'info' => [], | |
| ]; | |
| $normalized_responses[] = $response; | |
| } | |
| // Add normalized responses to the class property. | |
| $this->mockRequestResponses = array_merge($this->mockRequestResponses, $normalized_responses); | |
| protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void { | |
| // Normalize responses by applying defaults before storing. | |
| $normalized_responses = []; | |
| foreach ($responses as $response) { | |
| if (isset($response['response']) && !is_array($response['response'])) { | |
| throw new \InvalidArgumentException('Mocked request response "response" key must be an array.'); | |
| } | |
| // @phpstan-ignore-next-line nullCoalesce.offset | |
| $response['response'] = ($response['response'] ?? []) + [ | |
| 'ok' => TRUE, | |
| 'status' => 200, | |
| 'body' => '', | |
| 'error' => NULL, | |
| 'info' => [], | |
| ]; | |
| $normalized_responses[] = $response; | |
| } | |
| // Add normalized responses to the class property. | |
| $this->mockRequestResponses = array_merge($this->mockRequestResponses, $normalized_responses); |
🤖 Prompt for AI Agents
.vortex/tooling/tests/Traits/MockTrait.php around lines 229 to 245: the code
assumes $response['response'] is an array when merging defaults which will fatal
if a caller passes a non-array; validate that isset($response['response'])
implies is_array($response['response']) (or coerce only when it's null) and if
it is present but not an array throw a clear InvalidArgumentException (include
the offending index/key in the message) so callers get a descriptive failure;
after validation, proceed to merge the defaults into the array and append to
$this->mockRequestResponses as before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (4)
.vortex/tooling/rector.php (2)
38-49: ExpandwithPaths()to cover core tooling sources (e.g. helpers)Right now Rector only runs on the
notify*subdirectories plusplaygroundandtests. Any shared tooling files directly undersrc/(for examplesrc/helpers.php) will never be processed.If those core files are meant to be modernized and kept in sync with the notifier code, consider simplifying the paths to include the whole tooling
srctree:- ->withPaths([ - __DIR__ . '/src/notify', - __DIR__ . '/src/notify-email', - __DIR__ . '/src/notify-github', - __DIR__ . '/src/notify-jira', - __DIR__ . '/src/notify-newrelic', - __DIR__ . '/src/notify-slack', - __DIR__ . '/src/notify-webhook', - __DIR__ . '/playground', - __DIR__ . '/tests', - ]) + ->withPaths([ + __DIR__ . '/src', + __DIR__ . '/playground', + __DIR__ . '/tests', + ])This keeps the config simpler and ensures shared helpers are also refactored.
76-78: Align PHP target comment withwithPhpSets()configurationThe comment states this config “modernizes syntax to PHP 8.3” and includes rules “through 8.3”, but the actual config uses the PHP‑8.2 set:
// PHP version upgrade sets - modernizes syntax to PHP 8.3. // Includes all rules from PHP 5.3 through 8.3. ->withPhpSets(php82: TRUE)To avoid confusion, either:
- If 8.3 is the intended baseline, switch to the 8.3 set:
- ->withPhpSets(php82: TRUE) + ->withPhpSets(php83: TRUE)
- Or, if 8.2 is the real target for now, update the comment to reference PHP 8.2 (and 5.3–8.2) accordingly.
.vortex/tooling/phpcs.xml (1)
17-26: Consider whether explicitsrc/notify-*entries are needed.If PHPCS scans
srcrecursively (the usual default), the additionalsrc/notify*<file>entries are likely redundant; you can keep just<file>src</file>and<file>tests</file>unless you’ve observed non-recursive behaviour..github/workflows/vortex-test-tooling.yml (1)
53-57: Clarify mapping betweenGITHUB_TOKENenv andPACKAGE_TOKENsecret.The step exposes
GITHUB_TOKENbut sources it fromsecrets.PACKAGE_TOKEN. If the tooling expects a package token distinct from the standardGITHUB_TOKEN, consider renaming the env var (e.g.,PACKAGE_TOKEN) or, if it truly needs the default token, switch the secret reference tosecrets.GITHUB_TOKEN. Otherwise, add a brief comment explaining this mapping.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
.github/workflows/vortex-test-tooling.yml(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/phpstan.neon(1 hunks).vortex/tooling/playground/.env(1 hunks).vortex/tooling/playground/.env.local(1 hunks).vortex/tooling/playground/env.php(1 hunks).vortex/tooling/playground/formatters.php(1 hunks).vortex/tooling/playground/request.php(1 hunks).vortex/tooling/rector.php(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.github/workflows/vortex-test-tooling.yml
🧬 Code graph analysis (3)
.vortex/tooling/playground/formatters.php (1)
.vortex/tooling/src/helpers.php (1)
fail_no_exit(135-139)
.vortex/tooling/playground/env.php (1)
.vortex/tooling/src/helpers.php (1)
load_dotenv(39-71)
.vortex/tooling/playground/request.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(237-243)request_post(265-272)request(294-349)
🪛 dotenv-linter (4.0.0)
.vortex/tooling/playground/.env
[warning] 25-25: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 29-29: [UnorderedKey] The PLAYGROUND_FLOAT key should go before the PLAYGROUND_NUMBER key
(UnorderedKey)
[warning] 33-33: [UnorderedKey] The PLAYGROUND_BOOL_FALSE key should go before the PLAYGROUND_BOOL_TRUE key
(UnorderedKey)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-docs
🔇 Additional comments (7)
.vortex/tooling/playground/formatters.php (1)
1-15: LGTM! Clean file structure and setup.The shebang, strict types declaration, namespace, and documentation are all well-structured. The relative path for requiring helpers.php is correct for a playground script.
.vortex/tooling/rector.php (1)
13-37: Base Rector configuration structure looks sound
declare(strict_types=1);, the Rectoruseimports, and returningRectorConfig::configure()with chained methods follow Rector 2.x patterns and are internally consistent..vortex/tooling/phpstan.neon (1)
5-29: PHPStan configuration is strict and narrowly exempted.Level 9 with paths restricted to
src,playground, andtests, plus tightly scopedignoreErrorsrules for test-only patterns, is a solid setup with no obvious gaps..vortex/tooling/playground/.env (1)
4-39: Playground .env cases cover the loader semantics well.Sample variables exercise quoting,
=in values, overrides, comments, multi-word, and various primitive-like values; this aligns nicely with whatload_dotenv()supports..github/workflows/vortex-test-tooling.yml (1)
14-88: Workflow structure for tooling CI looks solid.Matrixed PHP setup, composer install/normalize, linting, coverage-aware tests, playground runs, and artifact plus Codecov uploads form a coherent CI loop for the tooling package.
.vortex/tooling/playground/.env.local (1)
4-16: Local override sample aligns with the env-loading demo.Overrides for
PLAYGROUND_OVERRIDEandPLAYGROUND_QUOTEDplus local-only and debug/env flags are consistent with the behaviour verified inplayground/env.php..vortex/tooling/playground/request.php (1)
19-123: HTTP request demo gives comprehensive coverage of helper behaviour.The script exercises GET/POST, custom methods, error statuses, and timeouts against httpbin, using the helpers as intended and keeping failures non-fatal for CI via printed diagnostics.
| <!-- Allow long array lines in tests. --> | ||
| <rule ref="Drupal.Arrays.Array.LongLineDeclaration"> | ||
| <exclude-pattern>*.Test\.php</exclude-pattern> | ||
| <exclude-pattern>*.TestCase\.php</exclude-pattern> | ||
| <exclude-pattern>*.test</exclude-pattern> | ||
| </rule> | ||
|
|
||
| <!-- Allow missing class description in tests. --> | ||
| <rule ref="Drupal.Commenting.ClassComment.Missing"> | ||
| <exclude-pattern>*.Test\.php</exclude-pattern> | ||
| <exclude-pattern>*.TestCase\.php</exclude-pattern> | ||
| <exclude-pattern>*.test</exclude-pattern> | ||
| </rule> | ||
|
|
||
| <!-- Allow missing function names in tests. --> | ||
| <rule ref="Drupal.Commenting.FunctionComment.Missing"> | ||
| <exclude-pattern>*.Test\.php</exclude-pattern> | ||
| <exclude-pattern>*.TestCase\.php</exclude-pattern> | ||
| <exclude-pattern>*.test</exclude-pattern> | ||
| </rule> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Verify that exclude-pattern globs match the intended test files.
Patterns like *.Test\.php, *.TestCase\.php, and *.test are meant to target test files, but PHPCS’ exclude-pattern handling (glob vs regex, escaping) can be subtle. Please confirm these patterns actually exclude your test files for the referenced sniffs and adjust to the recommended PHPCS style if needed.
🤖 Prompt for AI Agents
In .vortex/tooling/phpcs.xml around lines 28-47, the exclude-patterns use
backslash-escaped dots and possibly incorrect wildcards (e.g. "*.Test\.php",
"*.TestCase\.php", "*.test") which PHPCS treats as shell-style globs not regex;
update them to proper glob forms such as "*Test.php" and "*TestCase.php" (or
"**/*Test.php" / "**/*TestCase.php" to match subdirectories) and remove the
unnecessary backslash escapes; also verify whether "*.test" is intended (replace
with "*/*.test" or remove if not a real filename extension) so the rules
actually exclude your test files.
| echo "=== Environment Loading Demo ===\n\n"; | ||
|
|
||
| // Show current directory. | ||
| echo "Current directory: " . __DIR__ . "\n\n"; | ||
|
|
||
| // Check if .env files exist. | ||
| $env_file = __DIR__ . '/.env'; | ||
| $env_local_file = __DIR__ . '/.env.local'; | ||
|
|
||
| echo "Checking for .env files:\n"; | ||
| if (file_exists($env_file)) { | ||
| echo " ✓ .env file found\n"; | ||
| echo " Content preview:\n"; | ||
| $file_lines = file($env_file, FILE_IGNORE_NEW_LINES); | ||
| if ($file_lines !== FALSE) { | ||
| $lines = array_slice($file_lines, 0, 5); | ||
| foreach ($lines as $line) { | ||
| echo " " . $line . "\n"; | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| echo " ✗ .env file not found\n"; | ||
| echo " Run from playground directory where .env file exists\n"; | ||
| } | ||
|
|
||
| if (file_exists($env_local_file)) { | ||
| echo " ✓ .env.local file found\n"; | ||
| echo " Content preview:\n"; | ||
| $file_lines = file($env_local_file, FILE_IGNORE_NEW_LINES); | ||
| if ($file_lines !== FALSE) { | ||
| $lines = array_slice($file_lines, 0, 5); | ||
| foreach ($lines as $line) { | ||
| echo " " . $line . "\n"; | ||
| } | ||
| } | ||
| } | ||
| else { | ||
| echo " ✗ .env.local file not found\n"; | ||
| echo " Create .env.local to test override behavior\n"; | ||
| } | ||
|
|
||
| echo "\n"; | ||
|
|
||
| // Load environment files. | ||
| echo "Loading environment files...\n"; | ||
| load_dotenv([$env_file, $env_local_file]); | ||
| echo " ✓ Environment files loaded\n"; | ||
|
|
||
| echo "\n"; | ||
|
|
||
| // Display loaded variables. | ||
| echo "Environment variables loaded:\n"; | ||
|
|
||
| $test_vars = [ | ||
| 'PLAYGROUND_VAR_1', | ||
| 'PLAYGROUND_VAR_2', | ||
| 'PLAYGROUND_VAR_3', | ||
| 'PLAYGROUND_QUOTED', | ||
| 'PLAYGROUND_WITH_EQUALS', | ||
| 'PLAYGROUND_OVERRIDE', | ||
| ]; | ||
|
|
||
| foreach ($test_vars as $test_var) { | ||
| $value = getenv($test_var); | ||
| if ($value !== FALSE) { | ||
| echo " " . $test_var . " = " . $value . "\n"; | ||
| } | ||
| else { | ||
| echo " " . $test_var . " = (not set)\n"; | ||
| } | ||
| } | ||
|
|
||
| echo "\n"; | ||
|
|
||
| // Verify specific values. | ||
| echo "Verification tests:\n"; | ||
|
|
||
| if (getenv('PLAYGROUND_VAR_1') === 'value1') { | ||
| echo " ✓ PLAYGROUND_VAR_1 loaded correctly\n"; | ||
| } | ||
| else { | ||
| echo " ✗ PLAYGROUND_VAR_1 not loaded correctly\n"; | ||
| } | ||
|
|
||
| $quoted_value = getenv('PLAYGROUND_QUOTED'); | ||
| if ($quoted_value === 'quoted value with spaces' || $quoted_value === 'locally overridden with single quotes') { | ||
| echo " ✓ PLAYGROUND_QUOTED loaded correctly\n"; | ||
| echo " Value: " . $quoted_value . "\n"; | ||
| } | ||
| else { | ||
| echo " ✗ PLAYGROUND_QUOTED not loaded correctly\n"; | ||
| echo " Expected: 'quoted value with spaces' or 'locally overridden with single quotes'\n"; | ||
| echo " Got: " . $quoted_value . "\n"; | ||
| } | ||
|
|
||
| if (getenv('PLAYGROUND_WITH_EQUALS') === 'value=with=equals') { | ||
| echo " ✓ PLAYGROUND_WITH_EQUALS loaded correctly (handles = in value)\n"; | ||
| } | ||
| else { | ||
| echo " ✗ PLAYGROUND_WITH_EQUALS not loaded correctly\n"; | ||
| } | ||
|
|
||
| // Check override behavior. | ||
| if (file_exists($env_local_file)) { | ||
| $override_value = getenv('PLAYGROUND_OVERRIDE'); | ||
| if ($override_value === 'overridden_by_local') { | ||
| echo " ✓ PLAYGROUND_OVERRIDE correctly overridden by .env.local\n"; | ||
| echo " Original value in .env: original_value\n"; | ||
| echo " Overridden value from .env.local: " . $override_value . "\n"; | ||
| } | ||
| elseif ($override_value === 'original_value') { | ||
| echo " ✗ PLAYGROUND_OVERRIDE not overridden (still has .env value)\n"; | ||
| echo " Check that .env.local is loaded after .env\n"; | ||
| } | ||
| else { | ||
| echo " ✗ PLAYGROUND_OVERRIDE has unexpected value: " . $override_value . "\n"; | ||
| } | ||
| } | ||
|
|
||
| echo "\n"; | ||
|
|
||
| // Test comment handling. | ||
| echo "Comment handling:\n"; | ||
| if (getenv('PLAYGROUND_COMMENT') === FALSE) { | ||
| echo " ✓ Comment lines correctly ignored\n"; | ||
| } | ||
| else { | ||
| echo " ✗ Comment line was parsed as variable\n"; | ||
| } | ||
|
|
||
| echo "\n=== Demo Complete ===\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Env-loading demo correctly exercises quoting, overrides, and comment handling.
The flow—previewing files, calling load_dotenv([$env_file, $env_local_file]), then verifying key variables and override/comment semantics—matches the helper implementation and the playground .env/.env.local contents. If you want even stronger coverage, consider adding brief checks for PLAYGROUND_MULTIWORD, numeric, and boolean-like variables, but the current script is already effective as a manual demo.
🤖 Prompt for AI Agents
In .vortex/tooling/playground/env.php around lines 20 to 151, the demo approves
current tests but suggests stronger coverage; add brief checks that verify
PLAYGROUND_MULTIWORD (a multi-word unquoted value), PLAYGROUND_NUMERIC (numeric
value), and PLAYGROUND_BOOL_LIKE (true/false-like strings) are loaded and
reported correctly: fetch each with getenv(), print their values (or "(not
set)"), and include simple assertions that compare them to expected strings
(e.g., 'multi word', '12345', 'true' or 'false') with success/failure messages
matching the existing style so the script exercises quoting, numeric and
boolean-like parsing without changing the current flow.
| echo "=== Output Formatter Demo ===\n\n"; | ||
|
|
||
| echo "1. note() - Plain note output:\n"; | ||
| note("This is a plain note message"); | ||
| note("Note with formatting: %s %d", "text", 123); | ||
|
|
||
| echo "\n2. info() - [INFO] Cyan output:\n"; | ||
| info("This is an info message"); | ||
| info("Info with formatting: %s", "formatted text"); | ||
|
|
||
| echo "\n3. task() - [TASK] Blue output:\n"; | ||
| task("This is a task message"); | ||
| task("Task with formatting: %s", "running task"); | ||
|
|
||
| echo "\n4. pass() - [ OK ] Green output:\n"; | ||
| pass("This is a success message"); | ||
| pass("Success with formatting: %s completed", "operation"); | ||
|
|
||
| echo "\n5. fail_no_exit() - [FAIL] Red output (no exit):\n"; | ||
| fail_no_exit("This is a failure message (no exit)"); | ||
| fail_no_exit("Failure with formatting: %s failed", "operation"); | ||
|
|
||
| echo "\n6. Testing color detection:\n"; | ||
| note("Colors should appear in terminal with color support"); | ||
| note("If TERM=dumb or no tput, colors are stripped"); | ||
| info("Current TERM: %s", getenv('TERM') ?: 'not set'); | ||
|
|
||
| echo "\n7. Multiple formatters in sequence:\n"; | ||
| task("Starting multi-step process"); | ||
| note("Step 1: Initializing"); | ||
| note("Step 2: Processing data"); | ||
| note("Step 3: Validating results"); | ||
| pass("Multi-step process completed successfully"); | ||
|
|
||
| echo "\n=== Demo Complete ===\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Excellent demonstration of formatter functions.
The script provides a clear, progressive demonstration of all the formatter helpers (note, info, task, pass, fail_no_exit). The examples cover both plain and formatted messages, and the multi-step sequence illustrates practical usage patterns effectively.
Optional: For robustness, consider adding a file existence check before the require_once on line 15:
+if (!file_exists(__DIR__ . '/../src/helpers.php')) {
+ fwrite(STDERR, "Error: helpers.php not found. Run from playground directory.\n");
+ exit(1);
+}
require_once __DIR__ . '/../src/helpers.php';However, this is entirely optional for a playground script where the path relationship is fixed.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In .vortex/tooling/playground/formatters.php around lines 15–16, add a guard
before the require_once so the script checks that the file exists and is
readable (e.g., file_exists/is_readable) and only calls require_once when
present; if the file is missing, emit a clear error message and exit gracefully
to avoid a fatal require_once failure.
3086d40 to
b9aeaad
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.vortex/CLAUDE.md (1)
1-1613: Markdown formatting issues across the documentSimilar to the scripts_conversion_plan.md file, markdownlint has flagged several formatting inconsistencies (ordered list prefixes). These are minor style issues.
Consider running the markdown auto-formatter:
#!/bin/bash cd .vortex ahoy lint-markdown-fix
♻️ Duplicate comments (23)
.vortex/tooling/rector.php (1)
38-49: Past review comments remain valid.The previous review identified two issues that should be addressed:
Lines 38-49: The paths list specific
src/notify-*subdirectories but may excludesrc/helpers.php. Consider whether__DIR__ . '/src'should be included to ensure all core helpers are modernized.Lines 76-78: The comment states "modernizes syntax to PHP 8.3" and "rules from PHP 5.3 through 8.3," but the configuration uses
withPhpSets(php82: TRUE). Either update the comment to reflect PHP 8.2 as the target, or change tophp83: TRUEif PHP 8.3 is intended.Also applies to: 76-78
.vortex/tooling/src/notify-slack (1)
137-151: Past review comment remains valid.The previous review noted that the current implementation only validates HTTP status 200 but doesn't inspect the Slack webhook response body. Slack webhooks can return HTTP 200 with error messages (e.g., "invalid_payload", "no_text") in the body, which would currently be treated as success.
The past review suggested also checking that the response body equals "ok" for stricter validation. This remains an optional improvement to make misconfiguration or invalid payload failures more visible.
.vortex/tooling/phpcs.xml (2)
17-26: Past review comment remains valid.The previous review noted that lines 18-24 individually list
src/notify-*subdirectories, which may be redundant since line 17 already includes<file>src</file>and PHPCS scans directories recursively by default. Consider removing the individual subdirectory entries unless non-recursive behavior is specifically required.
28-47: Past review comment remains valid.The previous review identified that the exclude-patterns use backslash-escaped dots (e.g.,
*.Test\.php,*.TestCase\.php) which may not work as expected in PHPCS glob patterns. These should be updated to proper glob forms like*Test.phpand*TestCase.php(or**/*Test.php/**/*TestCase.phpfor subdirectories) without the unnecessary backslash escapes..vortex/tooling/src/notify-github (2)
61-71: Consider removing the upper bound on deployment ID length.The 11-digit upper limit could reject future larger GitHub deployment IDs. While the numeric validation is useful, consider relaxing the constraint to only enforce a minimum length and the
ctype_digit()check.Apply this diff:
- // Check length (9-11 digits) and numeric format. - return $length >= 9 && $length <= 11 && ctype_digit($id_str); + // Check minimum length (9+ digits) and numeric format. + return $length >= 9 && ctype_digit($id_str);
89-92: Add explicit Content-Type header for JSON POST requests.Both deployment creation and status update POSTs send JSON bodies but don't specify
Content-Type: application/json. While GitHub's API may handle this, setting the header explicitly prevents potential issues and follows best practices.Apply this diff to both POST calls:
$response = request_post($url, json_encode($payload_data), [ 'Authorization: token ' . $token, 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]);Also apply to line 147-150.
.vortex/tooling/src/notify-email (1)
117-121: Filter out empty recipient entries after parsing.If
VORTEX_NOTIFY_EMAIL_RECIPIENTScontains extra commas or whitespace, the currentexplode/trimlogic can produce empty strings, leading to blankTo:headers and unnecessary mail command invocations.Apply this diff:
// Parse and send to each recipient. $sent = []; -$recipients = array_map(trim(...), explode(',', $recipients)); +$recipients = array_filter( + array_map(trim(...), explode(',', $recipients)), + static fn(string $value): bool => $value !== '' +);.vortex/tooling/test_plan.md (1)
10-217: Fix markdownlint violations for consistency.The document has several formatting issues that are causing markdownlint failures:
- Surround all fenced code blocks with blank lines (MD031)
- Wrap bare URLs in angle brackets, e.g.,
<https://example.com>(MD034)- Restart ordered list numbering at 1 for the "Script Tests" section instead of continuing from 15 (MD029)
Example fixes:
**Mock response structure:** + ```php @@
@@
-15. testMockRequestGetScriptPassingSuccess
+1. testMockRequestGetScriptPassingSuccess
@@
-- - Mock: url="https://example.com"
+- - Mock: url="https://example.com"Apply similar fixes throughout the document. </blockquote></details> <details> <summary>.github/workflows/vortex-test-tooling.yml (1)</summary><blockquote> `53-58`: **Clarify environment variable naming.** Setting `GITHUB_TOKEN` from `secrets.PACKAGE_TOKEN` is potentially confusing. If the tooling expects `GITHUB_TOKEN`, consider using `secrets.GITHUB_TOKEN`. If elevated privileges from `PACKAGE_TOKEN` are required, add a comment explaining why. Example clarification: ```diff - name: Run tests run: composer test-coverage working-directory: .vortex/tooling env: + # Uses PACKAGE_TOKEN for elevated privileges needed by tooling tests GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }}.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
16-16: Consider importing functions and classes instead of using phpcs:disable.The
@phpcs:disabledirective suppresses warnings about fully qualified names, but the cleaner approach would be to import the functions and exception classes at the top of the file and use short names throughout.Add imports:
namespace DrevOps\VortexTooling\Tests\Self; use DrevOps\VortexTooling\Tests\Unit\UnitTestCase; +use InvalidArgumentException; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\AssertionFailedError; +use RuntimeException; +use function DrevOps\VortexTooling\request; +use function DrevOps\VortexTooling\request_get; +use function DrevOps\VortexTooling\request_post; /** * Self-tests for mocking of HTTP request functions. @@ - * @phpcs:disable Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing */Then replace all
\DrevOps\VortexTooling\request_*withrequest_*and exception FQNs with short names..vortex/tooling/src/notify (1)
73-73: Use DIRECTORY_SEPARATOR for cross-platform path construction.The hardcoded forward slash will break on Windows systems. Use PHP's
DIRECTORY_SEPARATORconstant for cross-platform compatibility.Apply this diff:
- $script = __DIR__ . ('/notify-' . $channel); + $script = __DIR__ . DIRECTORY_SEPARATOR . 'notify-' . $channel;.vortex/tooling/src/notify-jira (1)
107-112: Critical: Script continues after skipping issue extraction, causing undefined variable errors.The code still contains the issues previously flagged:
Unused helper function: The
extract_issue()function (lines 100-105) is defined but not used. The same regex is duplicated inline at lines 108-109.Missing exit: When no issue is found (line 109),
pass()is called but execution continues. This causes$issueto be undefined when referenced later (e.g., line 182), leading to runtime errors.These issues were marked as addressed in commit 9b2f100, but they appear to still be present in the code.
Apply this fix to use the helper and exit properly:
task('Extracting issue'); -if (!preg_match('/([^\/]+\/)?([A-Za-z0-9]+\-\d+)/', $label, $matches)) { +$issue = extract_issue($label); +if ($issue === NULL) { pass('Deployment label %s does not contain issue number.', $label); + exit(0); } -$issue = $matches[2]; note('Found issue %s.', $issue);.vortex/tooling/src/notify-webhook (2)
88-92: Add error handling forjson_encode()failure.The
json_escape_value()function doesn't handle the case wherejson_encode()returnsfalseon failure (e.g., malformed UTF-8 input). This will cause a type error insubstr()when it receivesfalseinstead of a string.Apply this diff:
function json_escape_value(string $value): string { $escaped = json_encode($value); + if ($escaped === FALSE) { + fail('Failed to JSON-encode value: ' . $value); + } // Remove surrounding quotes. return substr($escaped, 1, -1); }
136-138: Consider masking webhook URL in error message.The error message includes the full
$webhook_url, which may contain sensitive tokens or credentials in the URL path or query string. Since$webhook_domainis already sanitized (line 113), use it instead to avoid potential credential leakage in logs.Apply this diff:
if ($response['status'] !== (int) $expected_status) { - fail(sprintf('Unable to send notification to webhook %s.', $webhook_url)); + fail(sprintf('Unable to send notification to webhook %s/***', $webhook_domain)); }.vortex/tooling/src/notify-newrelic (1)
122-135: Add validation forjson_decode()result.The code decodes the API response without verifying that
json_decode()succeeded. If the response contains invalid JSON,$datawill benull, leading to potential "undefined array key" errors when accessing$data['applications'][0]['id'].Apply this diff:
if ($response['ok'] && $response['body']) { $data = json_decode((string) $response['body'], TRUE); - if (isset($data['applications'][0]['id'])) { + if ($data && is_array($data) && isset($data['applications'][0]['id'])) { $appid = $data['applications'][0]['id']; } }.vortex/tooling/CLAUDE.md (2)
128-138: Update documentation to use consistentmockRequest*method names.Lines 135-138 still reference the old
mockCurl*naming (mockCurl,mockCurlMultiple,mockCurlAssertAllMocksConsumed), but the actual MockTrait API (and the reference section at lines 714-720) usesmockRequest(),mockRequestGet(),mockRequestPost(),mockRequestMultiple(), andmockRequestAssertAllMocksConsumed().This inconsistency was previously flagged and marked as addressed in commit 9b2f100, but appears to remain in lines 135-138.
Apply this diff:
**Pattern**: ```php protected function mockPassthru(array $response): void protected function mockPassthruMultiple(array $responses): void protected function mockPassthruAssertAllMocksConsumed(): void protected function mockQuit(int $code = 0): void -protected function mockCurl(array $response): void -protected function mockCurlMultiple(array $responses): void -protected function mockCurlAssertAllMocksConsumed(): void +protected function mockRequest(string $url, array $options = [], array $response = []): void +protected function mockRequestGet(string $url, array $headers = [], int $timeout = 10, array $response = []): void +protected function mockRequestPost(string $url, $body = NULL, array $headers = [], int $timeout = 10, array $response = []): void +protected function mockRequestMultiple(array $responses): void +protected function mockRequestAssertAllMocksConsumed(): void--- `1-733`: **Consider addressing markdown linting issues for better consistency.** Static analysis has flagged several markdown formatting issues: - Fenced code blocks should be surrounded by blank lines (MD031) - Ordered list prefixes should consistently use `1.` style (MD029) - Bare URLs should be wrapped in `<...>` or converted to markdown links (MD034) These are minor formatting issues that don't affect functionality but would improve markdown consistency and pass linting checks. If you'd like to address these, the main changes would be: - Add blank lines before and after ` ```php ` and ` ``` ` blocks - Change numbered lists from `2.`, `3.`, etc. to all use `1.` (markdown auto-numbers) - Wrap URLs like `https://phpunit.de/documentation.html` in angle brackets or markdown link syntax </blockquote></details> <details> <summary>.vortex/tooling/tests/Traits/MockTrait.php (2)</summary><blockquote> `83-96`: **Update passthru docblock to match actual array keys.** The docblock at line 90 references `exit_code`, but the actual response array uses `result_code` (as seen in lines 119-123 and the property annotation at line 25). This inconsistency can confuse users of the trait. This was previously flagged and marked as addressed, but the docblock still contains the old naming. Apply this diff: ```diff * @param array<int, array{cmd:string, output?: string, result_code?: int, return?: NULL|FALSE}> $responses * Array of responses to return for each passthru call. * Each response should have: + * - cmd: Expected command (required) * - output: The output to echo - * - exit_code: The exit code to set (0 for success). + * - result_code: The exit code to set (optional, default: 0) + * - return: Return value (optional, NULL or FALSE)
229-245: Validateresponsestructure before applying defaults.At line 234, the code assumes
$response['response']is an array when merging with defaults using the+operator. If a caller accidentally passes a non-array value (e.g., a string), this will cause a fatal error with "unsupported operand types."This validation was previously suggested and marked as addressed, but no validation is present in the code.
Apply this diff:
protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void { // Normalize responses by applying defaults before storing. $normalized_responses = []; foreach ($responses as $response) { + if (isset($response['response']) && !is_array($response['response'])) { + throw new \InvalidArgumentException('Mocked request response "response" key must be an array.'); + } + // @phpstan-ignore-next-line nullCoalesce.offset $response['response'] = ($response['response'] ?? []) + [.vortex/tooling/src/helpers.php (4)
22-31: Add path traversal protection toexecute_override().The
$nameparameter is concatenated directly into a file path (line 25) without validation. While current usage passesbasename(__FILE__), the function signature accepts any string, creating a potential path traversal vulnerability if future usage passes untrusted input.Apply this diff to add validation:
function execute_override(string $name): void { + // Prevent path traversal by ensuring name doesn't contain separators. + if (str_contains($name, DIRECTORY_SEPARATOR) || str_contains($name, '/') || str_contains($name, '\\')) { + fail('Invalid script name for override: %s', $name); + } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); if ($dir) { $path = $dir . DIRECTORY_SEPARATOR . $name;
295-298: Remove unreachableexit(1)afterfail().The
exit(1)call on line 297 is unreachable dead code becausefail()already callsexit(1)internally (line 151).Apply this diff:
function request(string $url, array $options = []): array { if (!function_exists('curl_init')) { fail('curl extension is not available.'); - exit(1); }
300-302: Checkcurl_init()return value to prevent TypeError.
curl_init()can returnfalseon failure (e.g., invalid URL). Passingfalsetocurl_setopt_array()on line 322 will cause a TypeError in PHP 8.0+.Apply this diff:
$ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + } try {
175-179: Shell-escape$commandinvalidate_command()to prevent injection.The
$commandparameter is interpolated directly into a shell command (line 176) without escaping. If$commandever comes from untrusted input, this becomes a command-injection vector.Apply this diff:
function validate_command(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (41)
.github/workflows/vortex-test-tooling.yml(1 hunks).vortex/CLAUDE.md(9 hunks).vortex/tooling/.gitignore(1 hunks).vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/phpstan.neon(1 hunks).vortex/tooling/phpunit.xml(1 hunks).vortex/tooling/playground/.env(1 hunks).vortex/tooling/playground/.env.local(1 hunks).vortex/tooling/playground/env.php(1 hunks).vortex/tooling/playground/formatters.php(1 hunks).vortex/tooling/playground/request.php(1 hunks).vortex/tooling/rector.php(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/test_plan.md(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-failing(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-passing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-failing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-passing(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Traits/MockTrait.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/phpunit.xml.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/UnitTestCase.php.vortex/tooling/test_plan.md.vortex/tooling/tests/Fixtures/test-request-multiple.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Fixtures/test-request-get-passing.vortex/tooling/tests/Fixtures/test-request-post-passing
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/tests/Fixtures/test-quit-failing
📚 Learning: 2025-06-02T00:41:36.321Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1697
File: .vortex/installer/tests/Fixtures/install/provision_database_lagoon/docs/onboarding.md:16-17
Timestamp: 2025-06-02T00:41:36.321Z
Learning: DO NOT EVER REVIEW FILES IN .vortex/installer/tests/Fixtures/ directory - these are test fixtures and should not be reviewed under any circumstances.
Applied to files:
.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-failing
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.
Applied to files:
.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-failing
📚 Learning: 2025-06-03T03:15:29.849Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1714
File: scripts/vortex/doctor.sh:69-70
Timestamp: 2025-06-03T03:15:29.849Z
Learning: In the Vortex project, logging functions (task(), info(), note(), pass(), fail(), warn()) are intentionally duplicated across all script files to make each script self-contained and independent, rather than sharing them through a common library. This design choice prioritizes script independence over reducing code duplication.
Applied to files:
.vortex/CLAUDE.md
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Fixtures/test-passthru-passing
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.github/workflows/vortex-test-tooling.yml
🧬 Code graph analysis (11)
.vortex/tooling/playground/formatters.php (1)
.vortex/tooling/src/helpers.php (1)
fail_no_exit(135-139)
.vortex/tooling/tests/Unit/UnitTestCase.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (1)
mockTearDown(63-81)
.vortex/tooling/playground/env.php (1)
.vortex/tooling/src/helpers.php (1)
load_dotenv(39-71)
.vortex/tooling/playground/request.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(237-243)request_post(265-272)request(294-349)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (5)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)mockRequestAssertAllMocksConsumed(458-469)mockRequestMultiple(229-359).vortex/tooling/src/helpers.php (3)
request_get(237-243)request_post(265-272)request(294-349)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
__construct(20-26)
.vortex/tooling/tests/Self/MockQuitSelfTest.php (5)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (1)
quit(369-371)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
.vortex/tooling/tests/Traits/MockTrait.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-28).vortex/tooling/src/helpers.php (2)
fail(149-152)quit(369-371)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
__construct(20-26)
🪛 dotenv-linter (4.0.0)
.vortex/tooling/playground/.env
[warning] 25-25: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 29-29: [UnorderedKey] The PLAYGROUND_FLOAT key should go before the PLAYGROUND_NUMBER key
(UnorderedKey)
[warning] 33-33: [UnorderedKey] The PLAYGROUND_BOOL_FALSE key should go before the PLAYGROUND_BOOL_TRUE key
(UnorderedKey)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~140-~140: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
.vortex/CLAUDE.md
92-92: Ordered list item prefix
Expected: 1; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
93-93: Ordered list item prefix
Expected: 2; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
94-94: Ordered list item prefix
Expected: 3; Actual: 17; Style: 1/2/3
(MD029, ol-prefix)
95-95: Ordered list item prefix
Expected: 4; Actual: 18; Style: 1/2/3
(MD029, ol-prefix)
96-96: Ordered list item prefix
Expected: 5; Actual: 19; Style: 1/2/3
(MD029, ol-prefix)
97-97: Ordered list item prefix
Expected: 6; Actual: 20; Style: 1/2/3
(MD029, ol-prefix)
98-98: Ordered list item prefix
Expected: 7; Actual: 21; Style: 1/2/3
(MD029, ol-prefix)
99-99: Ordered list item prefix
Expected: 8; Actual: 22; Style: 1/2/3
(MD029, ol-prefix)
100-100: Ordered list item prefix
Expected: 9; Actual: 23; Style: 1/2/3
(MD029, ol-prefix)
101-101: Ordered list item prefix
Expected: 10; Actual: 24; Style: 1/2/3
(MD029, ol-prefix)
102-102: Ordered list item prefix
Expected: 11; Actual: 25; Style: 1/2/3
(MD029, ol-prefix)
103-103: Ordered list item prefix
Expected: 12; Actual: 26; Style: 1/2/3
(MD029, ol-prefix)
.vortex/tooling/scripts_conversion_plan.md
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
17-17: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
43-43: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
48-48: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
59-59: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
68-68: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
92-92: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
93-93: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
103-103: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
112-112: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
126-126: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
139-139: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
140-140: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
146-146: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
147-147: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
167-167: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
182-182: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
278-278: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
283-283: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
386-386: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
393-393: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
394-394: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
395-395: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
396-396: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
397-397: Ordered list item prefix
Expected: 4; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
398-398: Ordered list item prefix
Expected: 5; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
399-399: Ordered list item prefix
Expected: 6; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
401-401: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
402-402: Ordered list item prefix
Expected: 1; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
403-403: Ordered list item prefix
Expected: 2; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
404-404: Ordered list item prefix
Expected: 3; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
405-405: Ordered list item prefix
Expected: 4; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
406-406: Ordered list item prefix
Expected: 5; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
431-431: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
432-432: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
440-440: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
441-441: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
453-453: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
454-454: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
466-466: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
467-467: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
475-475: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
476-476: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
489-489: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
496-496: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
503-503: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
.vortex/tooling/CLAUDE.md
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
83-83: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
128-128: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
143-143: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
153-153: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
171-171: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
216-216: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
310-310: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
317-317: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
322-322: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
390-390: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
422-422: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
437-437: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
438-438: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
447-447: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
448-448: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
455-455: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
483-483: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
484-484: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
497-497: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
502-502: Ordered list item prefix
Expected: 1; Actual: 4; Style: 1/1/1
(MD029, ol-prefix)
646-646: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
727-727: Bare URL used
(MD034, no-bare-urls)
728-728: Bare URL used
(MD034, no-bare-urls)
729-729: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-installer (8.4)
🔇 Additional comments (34)
.vortex/tooling/tests/Fixtures/test-passthru-multiple (1)
1-23: LGTM! Clean fixture for testing multiple passthru callsThe fixture correctly demonstrates multiple
passthru()invocations with exit code capture. Structure is clean and aligns with the testing infrastructure..vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
20-26: LGTM! Well-designed exception for test mockingThe exception correctly validates non-zero exit codes and provides clear error messages. The symmetric design with
QuitSuccessExceptionenables clean test assertions..vortex/CLAUDE.md (3)
18-22: LGTM! Clear documentation of new tooling subsystemThe addition of the tooling directory structure and cross-reference to
.vortex/tooling/CLAUDE.mdproperly documents the new subsystem.
92-106: Excellent separation of concerns with dedicated tooling documentationThe clear warning to redirect users to
.vortex/tooling/CLAUDE.mdfor tooling-specific work prevents documentation duplication and confusion.
141-184: Testing architecture properly updated to reflect five systemsThe documentation accurately describes the addition of the tooling tests as the third system, with appropriate cross-references.
.vortex/tooling/tests/Fixtures/test-quit-passing (1)
1-20: LGTM! Effective fixture for testing quit() success pathThe unreachable code after
quit(0)serves as a validation check to ensure quit() prevents further execution. Clean implementation..vortex/tooling/playground/.env.local (1)
1-16: LGTM! Well-documented example environment fileThe file effectively demonstrates
.env.localoverride behavior with clear examples. The "secret" values are obviously example data (e.g.,secret_token_12345) appropriate for a playground environment..vortex/tooling/.gitignore (1)
2-2: Review comment is based on flawed reasoning about library lockfile practicesThe
.vortex/toolingpackage is declared as"type": "library"in its composer.json. According to Composer documentation and community practice, libraries may commit composer.lock but don't need to, and consumers won't be affected. Critically, installed packages ignore a library's lockfile, so the lockfile does not impact downstream projects using this library.While reproducible local/CI testing is a valid consideration for any project, the original comment frames ignoring
composer.lockas a problem specific to this package's testing needs. For a distributed library like this, not committing the lockfile is a legitimate and documented practice. The reasoning conflates application reproducibility (where a lockfile matters) with library distribution (where the lockfile has no effect on consumers).Likely an incorrect or invalid review comment.
.vortex/tooling/tests/Fixtures/test-request-get-failing (1)
1-24: LGTM - Test fixture for failing GET request scenario.This fixture appropriately tests the failure path for HTTP GET requests. The structure is clean and follows the same pattern as other fixtures in the test suite.
.vortex/tooling/tests/Fixtures/test-request-post-failing (1)
1-24: LGTM - Test fixture for failing POST request scenario.The fixture correctly demonstrates a failing POST request with JSON payload and appropriate headers. Implementation is consistent with other request fixtures.
.vortex/tooling/tests/Fixtures/test-passthru-passing (1)
1-19: LGTM - Test fixture for successful passthru operation.The fixture correctly tests a passing passthru scenario where the command exits with code 0 as expected.
.vortex/tooling/phpstan.neon (1)
1-29: LGTM - PHPStan configuration is appropriately strict.Level 9 analysis with targeted ignore rules for test files is a solid approach. The configuration correctly excludes vendor/node_modules and includes all relevant source paths.
.vortex/tooling/tests/Fixtures/test-request-get-passing (1)
1-24: LGTM - Test fixture for successful GET request scenario.The fixture appropriately demonstrates the success path, including response body output. Clean and consistent with the fixture suite pattern.
.vortex/tooling/tests/Fixtures/test-passthru-failing (1)
1-19: LGTM - Test fixture for mock-driven failing passthru scenario.While the command
echo "success"typically returns 0, this fixture likely tests that the mocking framework (MockTrait) can override the actual exit code to simulate failures. This pattern is consistent with testing mock behavior rather than actual command execution..vortex/tooling/phpunit.xml (1)
1-42: LGTM - Comprehensive PHPUnit configuration with appropriate strictness.The configuration includes:
- Proper bootstrap and strict error handling
- Coverage reporting with reasonable thresholds (50%/90%)
- JUnit logging for CI integration
- Appropriate source filtering including test traits and base cases
The schema version concern from the previous review has been addressed.
.vortex/tooling/tests/Fixtures/test-request-post-passing (1)
1-24: LGTM - Test fixture for successful POST request scenario.The fixture correctly demonstrates a successful POST with JSON payload and proper Content-Type header. Implementation is clean and consistent with the fixture suite.
.vortex/tooling/playground/.env (1)
1-39: Test fixture file - no review required.This is a playground/test fixture designed to demonstrate various .env loading behaviors (quotes, equals signs, multi-word values, etc.). Per project learnings, test fixture files should not be reviewed for code quality issues as they may intentionally contain edge cases and non-standard formatting for testing purposes.
The static analysis warnings (unquoted values, key ordering) are intentional test cases to validate the environment loader's handling of diverse input formats.
.vortex/tooling/tests/Fixtures/test-quit-failing (1)
1-20: Test fixture file - no review required.This is a test fixture located in the tests/Fixtures directory, designed to validate quit() behavior with exit code 1. Per project learnings, test fixture files should not be reviewed for code quality issues as they may intentionally contain specific patterns for testing purposes.
The unreachable echo statement on line 20 is intentional—it verifies that quit() properly terminates execution.
.vortex/tooling/tests/Fixtures/test-request-multiple (1)
1-26: Test fixture file - no review required.This is a test fixture in the tests/Fixtures directory, designed to validate multiple HTTP request calls in sequence (GET, POST, PUT). Per project learnings, test fixture files should not be reviewed for code quality issues as they serve specific testing purposes.
.vortex/tooling/playground/formatters.php (1)
1-51: LGTM - Playground demo script.This playground script effectively demonstrates the output formatter functions (note, info, task, pass, fail_no_exit) with clear examples and progressive scenarios. The structure is appropriate for a manual testing/demo script.
Note: A past review suggested adding a file existence check before the require_once on line 15, which remains an optional improvement for robustness.
.vortex/tooling/playground/request.php (1)
1-123: LGTM - Comprehensive HTTP request demo.This playground script provides excellent coverage of HTTP request scenarios across 9 tests, including success cases, error handling (404, 500), custom methods (PUT, DELETE), custom headers, and timeout behavior. The structure is clear and well-documented for manual testing purposes.
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
20-26: LGTM!The constructor correctly validates that only exit code 0 is accepted, mirroring the complementary
QuitErrorExceptiondesign. The implementation is clean and consistent with the testing infrastructure..vortex/tooling/tests/Unit/UnitTestCase.php (2)
19-25: LGTM!The tearDown order is correct: environment reset, mock cleanup, then parent tearDown. This ensures proper test isolation.
27-55: LGTM!The
runScriptmethod properly isolates script execution with output buffering and directory management. The try-finally pattern ensures directory restoration even on exceptions, and the coverage ignore markers on defensive error checks are appropriate..vortex/tooling/playground/env.php (1)
1-151: LGTM! Well-structured environment loading demonstration.The script effectively demonstrates:
- Loading from multiple .env files with local overrides
- Quote handling for values with spaces
- Preservation of equals signs in values
- Override behavior when .env.local exists
- Comment line ignoring
The implementation correctly exercises the
load_dotenv()helper and provides clear user feedback. The past suggestion to add checks for multiword, numeric, and boolean-like variables was a nitpick for enhanced coverage, but the current implementation adequately demonstrates the core functionality..vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
1-109: LGTM! Comprehensive quit mocking self-tests.The test suite effectively validates the
mockQuit()functionality:
Direct tests (lines 21-59): Verify that quit() throws the correct exception types (
QuitSuccessExceptionfor 0,QuitErrorExceptionfor non-zero) and validates exit code mismatches.Script tests (lines 61-107): Exercise quit() behavior through fixture scripts, ensuring proper exception propagation and verifying script output.
The test coverage is thorough, covering both success and failure pathways, and follows the documented naming conventions. The assertions correctly check for expected output and absence of error indicators.
.vortex/tooling/tests/Traits/MockTrait.php (1)
1-471: Overall: Well-architected mocking infrastructure with minor documentation inconsistencies.The MockTrait provides a robust, queue-based mocking system for passthru, quit, and HTTP request functions. The implementation correctly:
- Tracks mock state across multiple calls
- Validates expected vs. actual parameters (URL, method, command)
- Ensures all mocked responses are consumed via teardown checks
- Handles curl function chaining properly (init → setopt → exec → errno/error/getinfo)
- Increments indices before validation to mark mocks as consumed even on exceptions (lines 300-305)
The convenience methods (
mockRequestGet,mockRequestPost) provide good ergonomics by mirroring the actual helper function signatures.The issues flagged in other comments are minor documentation and validation improvements that don't affect the core functionality.
.vortex/tooling/src/helpers.php (1)
1-373: Overall: Well-designed helper utilities with security issues to address.The helpers module provides a solid foundation for the tooling package:
Strengths:
- Clean, focused functions with clear responsibilities
- Consistent output formatting with color support detection
- Structured HTTP request handling with proper error information
- Test-friendly
quit()wrapper that can be mocked- Good use of
try-finallyfor curl resource cleanup (lines 302-348)Areas for improvement:
The security and robustness issues flagged in other comments should be addressed:
- Path traversal protection in
execute_override()- Shell injection prevention in
validate_command()curl_init()failure handling inrequest()- Redundant
exit()cleanupOnce these issues are resolved, this will be a robust helper module.
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (6)
1-19: LGTM! Well-structured test class.The class structure, imports, and documentation are clear and appropriate for self-testing the passthru mocking functionality.
21-70: LGTM! Comprehensive coverage of success scenarios.These three tests effectively validate the passthru mocking for:
- Explicit default values (lines 21-37)
- Custom configurations (lines 39-55)
- Default behavior when only
cmdis provided (lines 57-70)The output buffering and assertions are correctly implemented.
72-109: LGTM! Proper validation of argument constraints.These tests correctly verify that:
- Missing
cmdkey throws appropriate exception (lines 72-89)- Invalid
returntype throws appropriate exception (lines 91-109)The use of try-finally blocks ensures output buffer cleanup, and
@phpstan-ignoreannotations are appropriate for intentionally invalid test inputs.
111-237: LGTM! Thorough testing of command validation and call counting.Excellent coverage of:
- Command mismatch detection (lines 111-126)
- Sequential mock consumption (lines 128-160)
- Over-calling detection (lines 162-206)
- Under-calling detection (lines 208-237)
The manual call to
mockPassthruAssertAllMocksConsumed()on line 236 is intentional and ensures the assertion failure occurs within the test method rather than during tearDown, providing clearer test semantics.
239-368: LGTM! Comprehensive script-based validation.These tests effectively mirror the direct passthru tests using the
runScript()utility, validating that mocking works correctly within script contexts. The tests cover:
- Passing/failing scenarios (lines 239-265)
- Custom configurations and defaults (lines 267-290)
- Argument validation errors (lines 292-317)
- Command mismatch detection (lines 319-331)
- Multiple call handling (lines 333-368)
The deliberate duplication between direct and script-based tests ensures both pathways are thoroughly validated.
370-413: LGTM! Complete coverage of script-based edge cases.The final tests appropriately cover:
- Under-calling detection with manual assertion (lines 370-399)
- Failing script validation (lines 401-413)
The manual call to
mockPassthruAssertAllMocksConsumed()on line 398 follows the same pattern as line 236, ensuring the assertion failure is attributed to the test method for clearer test reporting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (4)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
32-523: Import request helpers and exception classes instead of using fully‑qualified names throughoutThis file repeatedly references namespaced helpers and exception classes via leading‑backslash FQNs (e.g.
\DrevOps\VortexTooling\request_get,\RuntimeException,\InvalidArgumentException,\PHPUnit\Framework\AssertionFailedError). PHPCS has already flagged this pattern elsewhere in this file, and a patch was suggested in an earlier review.To satisfy coding standards and keep tests cleaner, import these once at the top and use short names in the body, for example:
namespace DrevOps\VortexTooling\Tests\Self; use DrevOps\VortexTooling\Tests\Unit\UnitTestCase; use PHPUnit\Framework\Attributes\CoversClass; +use InvalidArgumentException; +use PHPUnit\Framework\AssertionFailedError; +use RuntimeException; +use function DrevOps\VortexTooling\request; +use function DrevOps\VortexTooling\request_get; +use function DrevOps\VortexTooling\request_post; @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = request_get('https://example.com/api'); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); @@ - $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectException(AssertionFailedError::class); @@ - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class);…and similarly for the remaining
\request_*and exception references in this class.This will resolve the PHPCS
UseStatementMissingerrors without needing to permanently disable that sniff and keeps the tests more idiomatic..vortex/tooling/src/helpers.php (3)
43-52: Path traversal protection still needed inexecute_override.As noted in a previous review, the
$nameparameter is concatenated directly into a file path without validation, creating a potential path traversal vulnerability if future callers pass unsanitized input.
198-202: Command injection risk remains invalidate_command.As noted in a previous review,
$commandis interpolated directly into a shell string without escaping. Even if current callers only pass constants, usingescapeshellarg()is a safer practice.
325-325: Missing error handling forcurl_initfailure.As noted in a previous review,
curl_init()can returnfalseon failure. Passingfalsetocurl_setopt_array()will cause a TypeError.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
.vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Unit/EnvTest.php(1 hunks).vortex/tooling/tests/Unit/FormatterTest.php(1 hunks).vortex/tooling/tests/Unit/OverrideTest.php(1 hunks).vortex/tooling/tests/Unit/RequestTest.php(1 hunks).vortex/tooling/tests/Unit/TokenTest.php(1 hunks).vortex/tooling/tests/Unit/ValidateTest.php(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Unit/EnvTest.php.vortex/tooling/tests/Unit/FormatterTest.php.vortex/tooling/tests/Unit/ValidateTest.php.vortex/tooling/tests/Self/MockRequestSelfTest.php
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php
🧬 Code graph analysis (8)
.vortex/tooling/tests/Unit/OverrideTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/src/helpers.php (1)
execute_override(43-52).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockQuit(196-212)
.vortex/tooling/tests/Unit/TokenTest.php (1)
.vortex/tooling/src/helpers.php (1)
replace_tokens(216-226)
.vortex/tooling/tests/Unit/EnvTest.php (1)
.vortex/tooling/src/helpers.php (1)
load_dotenv(60-94)
.vortex/tooling/tests/Unit/RequestTest.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(262-268)request_post(290-297)request(319-376)
.vortex/tooling/tests/Unit/FormatterTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (2)
fail(172-175)_supports_color(240-242)
.vortex/tooling/tests/Unit/ValidateTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-28).vortex/tooling/src/helpers.php (3)
validate_variable(185-190)validate_command(198-203)fail(172-175).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(14-57)runScript(27-55).vortex/tooling/tests/Traits/MockTrait.php (5)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)mockRequestAssertAllMocksConsumed(458-469)mockRequestMultiple(229-359).vortex/tooling/src/helpers.php (3)
request_get(262-268)request_post(290-297)request(319-376)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-docs
🔇 Additional comments (3)
.vortex/tooling/tests/Unit/TokenTest.php (1)
18-72: Token replacement test coverage is accurate and aligned with helper semanticsThe data provider covers the key behaviors of
replace_tokens()(single/multiple tokens, no matches, repeated tokens) and matches the implementation inhelpers.php. Loading helpers insetUp()keeps the tests self-contained. No issues spotted..vortex/tooling/tests/Unit/OverrideTest.php (1)
27-97: Override execution tests correctly cover no‑op and executable scenariosThe four tests map cleanly onto the
execute_override()branches:
- Env var unset / dir without script / non‑executable script all correctly assert “no behavior”.
- Executable case uses
mockPassthruandmockQuitto validate the exact command, propagated exit code, and captured output.This aligns with the helpers implementation and gives good confidence in override behavior.
.vortex/tooling/tests/Unit/EnvTest.php (1)
27-118: Environment loader tests match helper behavior and cover key edge casesThe data provider and dedicated tests accurately reflect
load_dotenv()semantics:
- Trimming and quote removal,
- Values containing
=,- Whitespace handling,
- Skipping comments/empty lines,
- Graceful behavior for missing files.
Helper loading in
setUp()keeps tests independent. No issues found.
| public function testMockRequestGetSuccess(): void { | ||
| $this->mockRequestGet( | ||
| 'https://example.com/api', | ||
| [], | ||
| 10, | ||
| ['status' => 200, 'body' => 'success response'] | ||
| ); | ||
|
|
||
| // Load helpers.php AFTER setting up mocks. | ||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); | ||
|
|
||
| $this->assertTrue($result['ok']); | ||
| $this->assertEquals(200, $result['status']); | ||
| $this->assertEquals('success response', $result['body']); | ||
| $this->assertNull($result['error']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helpers are only loaded in the first test; later tests can fail when run in isolation
helpers.php is required only inside testMockRequestGetSuccess(). All subsequent tests in this class call \DrevOps\VortexTooling\request_get/post/request but never ensure the helpers are loaded themselves.
This couples the whole class to the execution order:
- Running
testMockRequestPostSuccess(or any later test) in isolation, or with randomized test order, can lead to fatalundefined functionerrors. - Tests should be independently runnable.
Recommend centralizing helper loading in setUp() and dropping the inline require_once in the first test, e.g.:
-class MockRequestSelfTest extends UnitTestCase {
-
- public function testMockRequestGetSuccess(): void {
- $this->mockRequestGet(
+class MockRequestSelfTest extends UnitTestCase {
+
+ protected function setUp(): void {
+ parent::setUp();
+ require_once __DIR__ . '/../../src/helpers.php';
+ }
+
+ public function testMockRequestGetSuccess(): void {
+ $this->mockRequestGet(
'https://example.com/api',
@@
- // Load helpers.php AFTER setting up mocks.
- require_once __DIR__ . '/../../src/helpers.php';
-
$result = \DrevOps\VortexTooling\request_get('https://example.com/api');The curl function mocks created in mockRequest* will still work correctly with this setup, since PHP resolves curl_* calls dynamically against the namespaced mocks.
🤖 Prompt for AI Agents
.vortex/tooling/tests/Self/MockRequestSelfTest.php lines 21-37: helpers.php is
only required inside testMockRequestGetSuccess causing later tests to fail when
run in isolation; move the require_once into the test class setUp() so helpers
are loaded before every test and remove the inline require_once from the test;
update setUp() to require_once __DIR__ . '/../../src/helpers.php' (or ensure
autoloading) so all tests can call
\DrevOps\VortexTooling\request_get/post/request independently and keep existing
mockRequest* curl mocks working.
| public static function providerOutputFormattersWithColor(): array { | ||
| return [ | ||
| 'task' => ['task', '[TASK]'], | ||
| 'info' => ['info', '[INFO]'], | ||
| 'pass' => ['pass', '[ OK ]'], | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add test coverage for fail_no_exit with color support.
The fail_no_exit formatter supports color output (red ANSI codes) but is missing from this data provider. This results in incomplete test coverage for colored output.
Apply this diff to add the missing test case:
public static function providerOutputFormattersWithColor(): array {
return [
'task' => ['task', '[TASK]'],
'info' => ['info', '[INFO]'],
'pass' => ['pass', '[ OK ]'],
+ 'fail_no_exit' => ['fail_no_exit', '[FAIL]'],
];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static function providerOutputFormattersWithColor(): array { | |
| return [ | |
| 'task' => ['task', '[TASK]'], | |
| 'info' => ['info', '[INFO]'], | |
| 'pass' => ['pass', '[ OK ]'], | |
| ]; | |
| } | |
| public static function providerOutputFormattersWithColor(): array { | |
| return [ | |
| 'task' => ['task', '[TASK]'], | |
| 'info' => ['info', '[INFO]'], | |
| 'pass' => ['pass', '[ OK ]'], | |
| 'fail_no_exit' => ['fail_no_exit', '[FAIL]'], | |
| ]; | |
| } |
🤖 Prompt for AI Agents
In .vortex/tooling/tests/Unit/FormatterTest.php around lines 106 to 112, the
data provider providerOutputFormattersWithColor is missing the colored entry for
the fail_no_exit formatter; add a new array entry for 'fail_no_exit' that maps
to the formatter name and the expected red-colored output (ANSI red start,
[FAIL], ANSI reset) so the provider includes ['fail_no_exit',
"\e[31m[FAIL]\e[0m"] (ensure proper PHP string escaping) to restore full color
test coverage.
2cde55e to
40fadd2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (34)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (2)
18-19: Loadhelpers.phpinsetUp()so tests don’t depend on method execution order
helpers.phpis currently required only intestMockRequestGetSuccess(), but all tests in this class callrequest_get/post/request. Running any later test in isolation (or with randomized order) can hitundefined functionfatals. Centralizing the require insetUp()keeps every test independently runnable and matches the earlier feedback.You can refactor like this:
-#[CoversClass(UnitTestCase::class)] -class MockRequestSelfTest extends UnitTestCase { - - public function testMockRequestGetSuccess(): void { +#[CoversClass(UnitTestCase::class)] +class MockRequestSelfTest extends UnitTestCase { + + protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + + public function testMockRequestGetSuccess(): void { @@ - // Load helpers.php AFTER setting up mocks. - require_once __DIR__ . '/../../src/helpers.php'; - - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = \DrevOps\VortexTooling\request_get('https://example.com/api');Also applies to: 21-38
7-8: Replace fully‑qualified helper/exception references with imports and drop the PHPCS suppressionThe file disables
Drupal.Classes.FullyQualifiedNamespace.UseStatementMissingand then uses\DrevOps\VortexTooling\request_*plus fully-qualified exception classes throughout. This is exactly what PHPCS is flagging in the tooling job and was already called out previously.Instead, import the functions and exception classes once and use short names in the body; then you can remove the suppression entirely:
namespace DrevOps\VortexTooling\Tests\Self; use DrevOps\VortexTooling\Tests\Unit\UnitTestCase; use PHPUnit\Framework\Attributes\CoversClass; +use InvalidArgumentException; +use PHPUnit\Framework\AssertionFailedError; +use RuntimeException; +use function DrevOps\VortexTooling\request; +use function DrevOps\VortexTooling\request_get; +use function DrevOps\VortexTooling\request_post; @@ /** * Self-tests for mocking of HTTP request functions. @@ - * - * @phpcs:disable Drupal.Classes.FullyQualifiedNamespace.UseStatementMissing */ @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = request_get('https://example.com/api'); @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = request_get('https://example.com/api'); @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = request_get('https://example.com/api'); @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/not-found'); + $result = request_get('https://example.com/not-found'); @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/error'); + $result = request_get('https://example.com/error'); @@ - $result = \DrevOps\VortexTooling\request_get('https://example.com/timeout'); + $result = request_get('https://example.com/timeout'); @@ - $result = \DrevOps\VortexTooling\request_post('https://example.com/api', '{"key":"value"}'); + $result = request_post('https://example.com/api', '{"key":"value"}'); @@ - $result = \DrevOps\VortexTooling\request_post( + $result = request_post( @@ - $result = \DrevOps\VortexTooling\request('https://example.com/resource', ['method' => 'PUT']); + $result = request('https://example.com/resource', ['method' => 'PUT']); @@ - $result1 = \DrevOps\VortexTooling\request_get('https://example.com/first'); + $result1 = request_get('https://example.com/first'); @@ - $result2 = \DrevOps\VortexTooling\request_post('https://example.com/second', 'data'); + $result2 = request_post('https://example.com/second', 'data'); @@ - $result3 = \DrevOps\VortexTooling\request('https://example.com/third', ['method' => 'PUT']); + $result3 = request('https://example.com/third', ['method' => 'PUT']); @@ - \DrevOps\VortexTooling\request_get('https://example.com/first'); - \DrevOps\VortexTooling\request_get('https://example.com/second'); + request_get('https://example.com/first'); + request_get('https://example.com/second'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/third'); + request_get('https://example.com/third'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/first'); - \DrevOps\VortexTooling\request_get('https://example.com/second'); + request_get('https://example.com/first'); + request_get('https://example.com/second'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/actual'); + request_get('https://example.com/actual'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/api'); + request_get('https://example.com/api'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/api'); + request_get('https://example.com/api'); @@ - \DrevOps\VortexTooling\request_get('https://example.com/api'); + request_get('https://example.com/api'); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); @@ - $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectException(AssertionFailedError::class); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); @@ - $this->expectException(\PHPUnit\Framework\AssertionFailedError::class); + $this->expectException(AssertionFailedError::class); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); @@ - $this->expectException(\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); @@ - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class);This should clear the PHPCS violations and keeps the tests cleaner and more idiomatic.
Also applies to: 16-16, 32-35, 54-57, 72-76, 92-96, 111-115, 131-136, 148-152, 164-172, 182-186, 211-221, 228-235, 242-247, 260-263, 277-283, 297-303, 316-320, 330-334, 349-353, 367-373, 388-394, 418-425, 435-438, 470-477, 487-491, 494-509, 512-523
.vortex/tooling/scripts_conversion_plan.md (1)
84-166: Align planned helper names with actual implementationThe planning document references functions like
load_env(),execute_override_if_exists(),require_env(), andrequire_command(), but the actual helpers.php implementation usesload_dotenv(),execute_override(),getenv_required(), andcommand_exists(). Updating these examples will prevent confusion when implementing or referencing this plan.Also applies to: 196-261, 408-417
.vortex/tooling/phpcs.xml (2)
21-30: Consider simplifying file specification if PHPCS scans recursivelyLines 22-28 individually list each
src/notify-*subdirectory, which may be redundant since line 21 already includes<file>src</file>. If PHPCS is configured to scan directories recursively (the default behavior), you can remove lines 22-28. Confirm PHPCS behavior and either remove the redundant entries or add a comment explaining why explicit enumeration is necessary.
37-56: Verify exclude-pattern glob syntax for test filesThe patterns
*.Test\.php,*.TestCase\.php, and*.testuse backslash-escaped dots, but PHPCS interpretsexclude-patternas shell-style globs where dots don't need escaping. Change them to*Test.php,*TestCase.php, or**/*Test.php/**/*TestCase.phpif you need to match subdirectories. Also verify whether*.testmatches real filenames or should be adjusted..vortex/tooling/src/notify-slack (3)
18-20: Remove blank line after inline comment to fix coding standard violationPipeline failure indicates a blank line following the inline comment separator at line 18 violates Drupal.Commenting.InlineComment.SpacingAfter. Remove the blank line so the comment is immediately followed by the next code line.
Apply this diff:
// ----------------------------------------------------------------------------- - // Slack notification project name.
62-64: Remove blank line after inline comment to fix coding standard violationPipeline failure indicates a blank line following the inline comment separator at line 62 violates Drupal.Commenting.InlineComment.SpacingAfter. Remove the blank line so the comment is immediately followed by the next code line.
Apply this diff:
// ------------------------------------------------------------------------------ - info('Started Slack notification.');
132-134: Optionally validate Slack response body for stricter error detectionCurrently the script only fails on non-200 HTTP status. Slack webhooks can return HTTP 200 with error strings in the body (e.g., "invalid_payload", "no_text"). Consider checking that the body equals "ok" or doesn't contain error indicators for more robust error detection.
Example enhancement:
if ($response['status'] !== 200) { fail('Unable to send notification to Slack. HTTP status: %s', $response['status']); } + +$body = trim($response['body'] ?? ''); +if ($body !== 'ok') { + fail('Slack webhook returned unexpected response: %s', $body); +}.vortex/tooling/tests/Unit/NotifyGithubTest.php (1)
24-32: Change test token value to avoid secret scanner false positiveThe hard-coded value
'ghp_test123456'matches GitHub's personal access token prefix and triggers Gitleaks as a generic API key, causing pipeline failures. Change it to a clearly fake value that won't match token patterns (e.g.,'test-token','TEST_GITHUB_TOKEN', or a split concatenation).Apply this diff:
$this->envSetMultiple([ - 'VORTEX_NOTIFY_GITHUB_TOKEN' => 'ghp_test123456', + 'VORTEX_NOTIFY_GITHUB_TOKEN' => 'test-github-token', 'VORTEX_NOTIFY_GITHUB_REPOSITORY' => 'owner/repo',You'll also need to update the token in mock assertions throughout the file (lines 48, 73, 91, 118, 134, 148, 161, 211).
.vortex/tooling/tests/Unit/GetenvTest.php (1)
137-171: Fix unreachable output assertions in exception testsTests at lines 137-146, 148-160, and 162-171 combine
expectException()withcaptureOutput(), but sincecaptureOutput()rethrows exceptions (line 187), the assignment to$outputnever completes and the subsequentassertStringContainsStringassertions are unreachable. The tests aren't validating the error messages.Fix by removing
expectException()and manually catching the exception:public function testGetenvRequiredWithNoVarsSetFails(): void { $this->mockQuit(1); - $this->expectException(QuitErrorException::class); - $output = $this->captureOutput(function (): void { - \DrevOps\VortexTooling\getenv_required('UNSET_VAR_1', 'UNSET_VAR_2'); - }); - - $this->assertStringContainsString('Missing required value for UNSET_VAR_1, UNSET_VAR_2', $output); + try { + $output = $this->captureOutput(function (): void { + \DrevOps\VortexTooling\getenv_required('UNSET_VAR_1', 'UNSET_VAR_2'); + }); + $this->fail('Expected QuitErrorException was not thrown'); + } + catch (QuitErrorException $e) { + $output = ob_get_clean() ?: ''; + $this->assertStringContainsString('Missing required value for UNSET_VAR_1, UNSET_VAR_2', $output); + } }Apply similar fixes to
testGetenvRequiredWithAllEmptyStringsFails()(lines 148-160) andtestGetenvRequiredWithSingleVarNotSetFails()(lines 162-171).Also applies to: 148-160, 162-171
.vortex/tooling/src/notify-newrelic (2)
95-116: Tighten app ID discovery: guard JSON decode and only log “Discovered” when ID is actually foundIn the discovery block, two things are slightly off:
$data = json_decode(...)is not validated before indexing, andpass('Discovered application ID: %s', $newrelic_appid);runs even when no ID was resolved or the API call failed (so it can claim discovery with an empty ID).You can keep behavior but make the control flow stricter:
if ($response['ok'] && $response['body']) { $data = json_decode((string) $response['body'], TRUE); - if (isset($data['applications'][0]['id'])) { - $newrelic_appid = $data['applications'][0]['id']; - } - - if (!$newrelic_appid) { - pass('Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', $newrelic_app_name); - quit(); - } - } - - pass('Discovered application ID: %s', $newrelic_appid); + if (is_array($data) && isset($data['applications'][0]['id'])) { + $newrelic_appid = $data['applications'][0]['id']; + } + + if (!$newrelic_appid) { + pass( + 'Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', + $newrelic_app_name, + ); + quit(); + } + + pass('Discovered application ID: %s', $newrelic_appid); + }This keeps the “skip” behavior for non-configured environments and avoids a misleading “Discovered application ID” message when nothing was resolved, while also avoiding indexing into a non-array decode result.
78-80: PHPCS: remove blank line after separator commentThere is a blank line immediately after the separator comment, which Drupal’s
InlineComment.SpacingAfterrule flags (same issue as in earlier runs):-// ------------------------------------------------------------------------------ - -info('Started New Relic notification.'); +// ------------------------------------------------------------------------------ +info('Started New Relic notification.');.vortex/tooling/src/notify-webhook (2)
16-17: PHPCS: remove blank lines after separator commentsBoth separator comments are followed by a blank line, which violates Drupal’s
InlineComment.SpacingAfterrule already reported by tooling:-// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- @@ -// ----------------------------------------------------------------------------- - +// -----------------------------------------------------------------------------Removing the blank lines will keep coding standards happy.
Also applies to: 63-64
44-49: Harden header parsing to drop empty entries fromVORTEX_NOTIFY_WEBHOOK_HEADERSIf
VORTEX_NOTIFY_WEBHOOK_HEADERShas trailing or repeated|,explode('|', $webhook_headers)yields empty strings which are then passed through toCURLOPT_HTTPHEADER. Cheap to filter them out:-// Webhook notification pipe-separated headers. +// Webhook notification pipe-separated headers. @@ -$webhook_headers = getenv_default('VORTEX_NOTIFY_WEBHOOK_HEADERS', 'Content-type: application/json'); +$webhook_headers = getenv_default('VORTEX_NOTIFY_WEBHOOK_HEADERS', 'Content-type: application/json'); @@ -$response = request($webhook_url, [ - 'method' => $webhook_method, - 'headers' => array_map(trim(...), explode('|', $webhook_headers)), - 'body' => $webhook_payload, -]); +$response = request($webhook_url, [ + 'method' => $webhook_method, + 'headers' => array_filter( + array_map(trim(...), explode('|', $webhook_headers)), + static fn(string $header): bool => $header !== '' + ), + 'body' => $webhook_payload, +]);This keeps the request cleaner and avoids passing meaningless empty header values to cURL.
Also applies to: 97-102
.vortex/tooling/src/notify-github (2)
47-66: Relax deployment ID length constraint to avoid future breakage
deployment_id_is_valid()enforces a 9–11 digit length:$length = strlen($id_str); // Check length (9-11 digits) and numeric format. return $length >= 9 && $length <= 11 && ctype_digit($id_str);This is brittle if GitHub ever changes ID size (or if current IDs outside that range appear). Keeping the numeric check but dropping the upper bound (and possibly the lower bound) makes the validation future‑proof while still rejecting obviously bad values:
- $length = strlen($id_str); - - // Check length (9-11 digits) and numeric format. - return $length >= 9 && $length <= 11 && ctype_digit($id_str); + $length = strlen($id_str); + + // Check length (at least 1 digit) and numeric format. + return $length >= 1 && ctype_digit($id_str);Adjust the comment accordingly.
23-24: Coding standards and GitHub API robustness: remove blank after separator and set JSON Content‑TypeTwo small, related tweaks:
- PHPCS separator comment – same as other scripts, there’s a blank line after the separator:
-// ----------------------------------------------------------------------------- - +// -----------------------------------------------------------------------------
- Explicit JSON
Content-Typefor GitHub POSTs – both deployment creation and status update send JSON bodies, but only setAccept. It’s safer to be explicit:$response = request_post($url, json_encode($payload_data), [ 'Authorization: token ' . $github_token, 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]); @@ $response = request_post($url, json_encode($status_data), [ - 'Accept: application/vnd.github.v3+json', - 'Authorization: token ' . $github_token, + 'Accept: application/vnd.github.v3+json', + 'Authorization: token ' . $github_token, + 'Content-Type: application/json', ]);Together this keeps both PHPCS and the GitHub API interactions tidy.
Also applies to: 84-88, 146-150
.vortex/tooling/tests/Unit/RequestTest.php (1)
10-100: Avoid real network calls in unit tests; prefer mocked or isolated integration testsThese tests exercise
request_*by callingexample.comdirectly (including timeouts and non‑200 codes). That ties the unit suite to external network/DNS andexample.com’s behavior, making it flaky and environment‑dependent.Consider instead:
- Using your existing helpers/mocks (e.g.,
MockTrait+mockRequest*) to testrequest_get,request_post, andrequestbehavior against predictable fake responses; or- Moving these into a clearly marked integration group that can be skipped when network access isn’t available.
That keeps unit runs deterministic while still validating the HTTP helpers where needed.
.vortex/tooling/src/notify-email (3)
100-114: Reuse a single timestamp for subject and body tokens.
date('d/m/Y H:i:s T')is computed twice, so%timestamp%in subject and body can drift by a second. Compute it once and reuse:[ suggest_optional_refactor ]
-$email_subject = replace_tokens($email_subject, [ - 'project' => $notify_project, - 'label' => $notify_label, - 'timestamp' => date('d/m/Y H:i:s T'), - 'environment_url' => $notify_env_url, - 'login_url' => $notify_login_url, -]); - -$email_message = replace_tokens($email_message, [ - 'project' => $notify_project, - 'label' => $notify_label, - 'timestamp' => date('d/m/Y H:i:s T'), - 'environment_url' => $notify_env_url, - 'login_url' => $notify_login_url, -]); +$timestamp = date('d/m/Y H:i:s T'); + +$email_subject = replace_tokens($email_subject, [ + 'project' => $notify_project, + 'label' => $notify_label, + 'timestamp' => $timestamp, + 'environment_url' => $notify_env_url, + 'login_url' => $notify_login_url, +]); + +$email_message = replace_tokens($email_message, [ + 'project' => $notify_project, + 'label' => $notify_label, + 'timestamp' => $timestamp, + 'environment_url' => $notify_env_url, + 'login_url' => $notify_login_url, +]);
128-160: Sanitize email header fields to prevent header injection (CRITICAL).
$email_from,$email_subject,$namecome from environment input and are interpolated directly intoTo:,Subject:andFrom:headers (and into thesendmail -fargument) without stripping CR/LF. An attacker controlling these values can inject additional headers or recipients via\r/\n.Introduce a small sanitizer and apply it before composing headers and shell commands:
@@ namespace DrevOps\VortexTooling; require_once __DIR__ . '/helpers.php'; execute_override(basename(__FILE__)); +/** + * Sanitize email header value to prevent header injection. + */ +function sanitize_email_header(string $value): string { + return str_replace(["\r", "\n", "\0"], '', $value); +} @@ -$email_recipients = array_map(trim(...), explode(',', $email_recipients)); +$email_recipients = array_map(trim(...), explode(',', $email_recipients)); @@ $sent = []; foreach ($email_recipients as $email_recipient) { $parts = array_map(trim(...), explode('|', $email_recipient, 2)); $email = $parts[0]; $name = $parts[1] ?? ''; + + // Sanitize values used in headers. + $email = sanitize_email_header($email); + $name = sanitize_email_header($name); + $safe_subject = sanitize_email_header($email_subject); + $safe_from = sanitize_email_header($email_from); - $to = empty($name) ? $email : sprintf('"%s" <%s>', $name, $email); + $to = empty($name) + ? $email + : sprintf('"%s" <%s>', addslashes($name), $email); @@ if ($has_sendmail) { $mail_content = sprintf('To: %s%s', $to, PHP_EOL); - $mail_content .= sprintf('Subject: %s%s', $email_subject, PHP_EOL); - $mail_content .= sprintf('From: %s%s', $email_from, PHP_EOL); + $mail_content .= sprintf('Subject: %s%s', $safe_subject, PHP_EOL); + $mail_content .= sprintf('From: %s%s', $safe_from, PHP_EOL); @@ - $cmd = sprintf('echo %s | sendmail -t -f %s', escapeshellarg($mail_content), escapeshellarg($email_from)); + $cmd = sprintf('echo %s | sendmail -t -f %s', escapeshellarg($mail_content), escapeshellarg($safe_from)); @@ } elseif ($has_mail) { - $mail_content = "From: {$email_from}\n\n{$email_message}"; - $cmd = sprintf('echo %s | mail -s %s %s', escapeshellarg($mail_content), escapeshellarg($email_subject), escapeshellarg($to)); + $mail_content = "From: {$safe_from}\n\n{$email_message}"; + $cmd = sprintf( + 'echo %s | mail -s %s %s', + escapeshellarg($mail_content), + escapeshellarg($safe_subject), + escapeshellarg($to) + );This keeps header values single-line and blocks injected headers while preserving current behaviour for valid inputs.
128-160: Harden recipient parsing and improve observability of send failures.Empty items from
explode(',', $email_recipients)(extra commas/whitespace) currently produce blankTo:headers and pointlessexec()calls, and failures are silent.Filter out empty recipients and optionally track/log failures per address:
-$email_recipients = array_map(trim(...), explode(',', $email_recipients)); +$email_recipients = array_filter( + array_map(trim(...), explode(',', $email_recipients)), + static fn(string $value): bool => $value !== '' +); -$sent = []; +$sent = []; +$failed = []; @@ - exec($cmd, $output, $return_var); - - if ($return_var === 0) { - $sent[] = $email; - } + exec($cmd, $output, $return_var); + + if ($return_var === 0) { + $sent[] = $email; + } + else { + $failed[] = $email; + note(sprintf('Failed to send email to: %s', $email)); + } @@ - exec($cmd, $output, $return_var); - - if ($return_var === 0) { - $sent[] = $email; - } + exec($cmd, $output, $return_var); + + if ($return_var === 0) { + $sent[] = $email; + } + else { + $failed[] = $email; + note(sprintf('Failed to send email to: %s', $email)); + } @@ -if (!empty($sent)) { - pass('Email notification sent successfully to %d recipient(s).', count($sent)); +if (!empty($sent)) { + pass('Email notification sent successfully to %d recipient(s).', count($sent)); + if (!empty($failed)) { + note('Failed to send email notification to %d recipient(s).', count($failed)); + }This avoids invalid recipients and makes it easier to debug partial failures.
.vortex/tooling/tests/Unit/TokenTest.php (1)
32-55: Consider adding data-provider cases that exercise JSON escaping inreplace_tokens().Current scenarios cover basic replacement but not values that need escaping (quotes, backslashes, newlines), even though
replace_tokens()usesjson_encode()internally.You could extend
dataProviderReplaceTokens()with cases where replacements include:
- A string containing double quotes (
He said "hi").- A Windows-style path with backslashes (
C:\folder\file).- A multi-line value with
\n.Asserting the exact escaped output would lock in this behaviour and make regressions in the escaping logic easier to catch, based on
replace_tokens()in.vortex/tooling/src/helpers.php..vortex/tooling/tests/Unit/OverrideTest.php (1)
26-64: Use EnvTrait helpers instead of directputenv()in tests.These tests manipulate
VORTEX_TOOLING_CUSTOM_DIRviaputenv(), bypassing EnvTrait’s tracking and automatic cleanup inUnitTestCase. That can leak state into other tests.Replace direct
putenv()calls with EnvTrait methods:public function testExecuteOverrideNoCustomDir(): void { - putenv('VORTEX_TOOLING_CUSTOM_DIR'); + $this->envUnset('VORTEX_TOOLING_CUSTOM_DIR'); @@ public function testExecuteOverrideCustomDirNoFile(): void { $custom_dir = self::$tmp . '/custom'; mkdir($custom_dir, 0777, TRUE); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir); @@ public function testExecuteOverrideCustomDirNonExecutable(): void { @@ - chmod($script_path, 0644); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + chmod($script_path, 0644); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir); @@ public function testExecuteOverrideWithExecutable(): void { @@ - chmod($script_path, 0755); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + chmod($script_path, 0755); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);This keeps environment mutations under the same control as the rest of the tooling tests. Based on learnings.
.vortex/tooling/tests/Unit/FormatterTest.php (2)
33-107: Avoid directputenv()forTERM; use EnvTrait helpers for consistent env management.The formatter tests manipulate
TERMwithputenv(), which bypasses EnvTrait tracking and teardown inUnitTestCase. This can leave the process environment in a surprising state for other tests.Update to use
envSet/envUnset:#[DataProvider('dataProviderOutputFormatters')] public function testOutputFormatters(string $function, string $expected_prefix): void { - putenv('TERM=dumb'); + $this->envSet('TERM', 'dumb'); @@ #[DataProvider('dataProviderOutputFormattersWithColor')] public function testOutputFormattersWithColor(string $function, string $expected_prefix): void { - putenv('TERM=xterm-256color'); + $this->envSet('TERM', 'xterm-256color'); @@ #[DataProvider('dataProviderTermSupportsColor')] public function testTermSupportsColor(string|bool $term_value, bool $expected): void { if ($term_value === FALSE) { - putenv('TERM'); + $this->envUnset('TERM'); } else { - putenv('TERM=' . $term_value); + $this->envSet('TERM', $term_value); }This keeps env mutations under EnvTrait’s control and consistent with other tests. Based on learnings.
77-118: Broaden formatter/color test coverage (fail_no_exitand positive color support).Two small gaps in coverage:
fail_no_exitis not included indataProviderOutputFormattersWithColor(), so its colored output isn’t exercised.dataProviderTermSupportsColor()only includes cases where color is not supported; there’s no positive case.You can address both via the data providers:
public static function dataProviderOutputFormattersWithColor(): array { return [ 'task' => ['task', '[TASK]'], 'info' => ['info', '[INFO]'], 'pass' => ['pass', '[ OK ]'], + 'fail_no_exit' => ['fail_no_exit', '[FAIL]'], ]; } @@ public static function dataProviderTermSupportsColor(): array { return [ 'dumb terminal' => ['term_value' => 'dumb', 'expected' => FALSE], 'no terminal' => ['term_value' => FALSE, 'expected' => FALSE], + 'color terminal' => ['term_value' => 'xterm-256color', 'expected' => TRUE], ]; }This ensures
fail_no_exitis covered in color-mode output and thatterm_supports_color()is asserted for a color-capableTERMas well..vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
61-107: Script-based tests: output assertions afterexpectException()are unreachableIn the script tests (
testMockQuitScript0Success,testMockQuitScript0Failure,testMockQuitScript1Success,testMockQuitScript1Failure) you:
- Call
mockQuit(...).- Configure
expectException(...)/expectExceptionMessage(...).- Call
$this->runScript(...).- Then assert on
$output.With PHPUnit, once the expected exception is thrown out of
runScript(), the test method stops executing; the$outputassignment and subsequent assertions never run. As a result, these tests do not currently validate the script output.Given the new
UnitTestCase::runScript/runScriptEarlyPass/runScriptErrorhelpers, consider one of these patterns instead:
- Manual catch pattern (no
expectException):- $this->mockQuit(); - - $this->expectException(QuitSuccessException::class); - $this->expectExceptionCode(0); - - $output = $this->runScript('tests/Fixtures/test-quit-passing'); - - $this->assertStringContainsString('Script will exit with code 0', $output); - $this->assertStringNotContainsString('ERROR Script continued after quit()', $output); + $this->mockQuit(); + + try { + $this->runScript('tests/Fixtures/test-quit-passing'); + $this->fail('Expected QuitSuccessException to be thrown'); + } + catch (QuitSuccessException $e) { + $this->assertSame(0, $e->getCode()); + $output = $e->getOutput(); + $this->assertStringContainsString('Script will exit with code 0', $output); + $this->assertStringNotContainsString('ERROR Script continued after quit()', $output); + }
- Or switch to
runScriptEarlyPass/runScriptErrorand let them assert on the output internally, without addingexpectExceptionor extra assertions in these tests..vortex/tooling/tests/Self/CheckNoExitSelfTest.php (3)
29-36: Use a unique temporary directory per test run and avoid mkdir() warningsReusing a fixed
$this->testDirand callingmkdir()on it and its subdirectories in everysetUp()risks:
- PHP warnings when directories already exist (often fatal with
failOnWarning="true").- Leftover fixtures from previous tests (e.g., files with
exit()) breakingtestHandlesEmptyDirectories()and making tests order-dependent.Create a per-test directory (e.g., with
uniqid()) and use recursivemkdir()so parents are created as needed:protected function setUp(): void { parent::setUp(); $this->scriptPath = dirname(__DIR__, 2) . '/check-no-exit.php'; - $this->testDir = self::$tmp . '/check-exit-test'; - mkdir($this->testDir); - mkdir($this->testDir . '/src'); - mkdir($this->testDir . '/tests'); + $this->testDir = self::$tmp . '/check-exit-test-' . uniqid('', true); + mkdir($this->testDir . '/src', 0777, true); + mkdir($this->testDir . '/tests', 0777, true); }
47-50: Factor out theexec()invocation and escape shell argumentsEach test builds the same
exec('cd %s && php %s 2>&1', …)inline and does not escape$this->testDir/$this->scriptPath. This is:
- Repetitive across six call sites.
- Fragile if paths ever contain spaces or shell metacharacters.
Consider extracting a small helper that escapes both arguments and returns
[$output, $exit_code], then reuse it across tests. For example:/** * Run check-no-exit.php from the current test directory. * * @return array{0: string[], 1: int} */ private function runCheckNoExitScript(): array { $output = []; $exit_code = 0; $cmd = sprintf( 'cd %s && php %s 2>&1', escapeshellarg($this->testDir), escapeshellarg($this->scriptPath), ); exec($cmd, $output, $exit_code); return [$output, $exit_code]; }And in tests:
[$output, $exit_code] = $this->runCheckNoExitScript();Apply the pattern to all the listed exec() sites.
Also applies to: 65-68, 82-85, 103-106, 120-123, 133-136
113-126: Also assert on the non‑zero exit code in the line‑number test
testReportsCorrectLineNumbers()currently only checks that'line-test.php:5'appears in the output. To ensure the checker fails the build when anexit()is detected, also assert that$exit_codeis non‑zero, consistent with your other “exit detected” tests:$output_str = implode("\n", $output); - $this->assertStringContainsString('line-test.php:5', $output_str); + $this->assertEquals(1, $exit_code); + $this->assertStringContainsString('line-test.php:5', $output_str);.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (1)
37-52: New Relic deployment payload callbacks are not exercised due tomockRequestPost()limitationsHere and in other tests you use a
$this->callback(function ($body) { … })argument tomockRequestPost()to assert on the JSON deployment payload (revision, user, description, changelog, etc.). Given the currentMockTrait::mockRequestPost()implementation only forwardsurl,method, andresponsetomockRequestMultiple, those callbacks are never invoked, so these assertions don’t actually run.Once you update the mocking helper as suggested in
NotifySlackTest(propagating body/headers/timeout throughmockRequestPost/mockRequestMultipleand having the stub invoke any callback on the body), these tests will start validating the New Relic request payloads as intended..vortex/tooling/tests/Unit/NotifyJiraTest.php (1)
48-57: JIRA comment‑payload callbacks suffer from the samemockRequestPost()limitationIn this test (and others) you pass a
$this->callback(function ($body) { … })tomockRequestPost()to inspect the JSON body sent to JIRA. As noted in the Slack/New Relic tests, the currentMockTrait::mockRequestPost()forwards onlyurl,method, andresponsetomockRequestMultiple, so the$bodycallback is never invoked and these payload assertions never execute.After you update the mocking helper to propagate and use the body/headers/timeout data, the JIRA tests that rely on body callbacks will start validating the request content as intended.
.vortex/tooling/src/helpers.php (4)
43-52: Path traversal vulnerability remains unaddressedThe past review correctly identified that
$nameis concatenated directly into a file path without validation (line 46). While current usage may passbasename(__FILE__), the function signature accepts any string, creating a path traversal vulnerability if called with untrusted input or if usage evolves.As recommended in the past review, validate
$namebefore use:
- Ensure it contains no directory separators (
/,\,DIRECTORY_SEPARATOR)- Verify the resolved path remains within
VORTEX_TOOLING_CUSTOM_DIR- Check the file is executable before calling
passthruBased on past review comments.
278-283: Shell command injection risk in command_exists()The
$commandparameter is interpolated directly into a shell command without escaping (line 279). This creates a command injection vulnerability if$commandever contains shell metacharacters.Apply the fix suggested in past reviews:
function command_exists(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }Based on past review comments.
404-404: Missing curl_init() failure check
curl_init()can returnfalseon failure. Passingfalsetocurl_setopt_array()on line 426 will cause a TypeError in PHP 8+.Apply the fix from past reviews:
$ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + }Based on past review comments.
426-426: Missing curl_setopt_array() failure check
curl_setopt_array()returnsfalseif setting options fails, leaving the cURL handle misconfigured. The request would proceed with incorrect settings.Add error handling after the call:
curl_setopt_array($ch, $opts); + if (curl_error($ch)) { + fail('Failed to set curl options: %s', curl_error($ch)); + }Note: This is a separate issue from the
curl_init()check above; both should be addressed.Based on past review comments.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (32)
.vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/plan.test-notify.md(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Self/CheckNoExitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Unit/CommandExistsTest.php(1 hunks).vortex/tooling/tests/Unit/DotenvTest.php(1 hunks).vortex/tooling/tests/Unit/FormatterTest.php(1 hunks).vortex/tooling/tests/Unit/GetenvTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyEmailTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyGithubTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyJiraTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyNewrelicTest.php(1 hunks).vortex/tooling/tests/Unit/NotifySlackTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyWebhookTest.php(1 hunks).vortex/tooling/tests/Unit/OverrideTest.php(1 hunks).vortex/tooling/tests/Unit/RequestTest.php(1 hunks).vortex/tooling/tests/Unit/TokenTest.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/tests/Unit/GetenvTest.php.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Unit/TokenTest.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Unit/DotenvTest.php.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/CheckNoExitSelfTest.php.vortex/tooling/tests/Unit/FormatterTest.php.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Unit/UnitTestCase.php.vortex/tooling/tests/Unit/CommandExistsTest.php.vortex/tooling/tests/Unit/NotifyEmailTest.php.vortex/tooling/tests/Unit/NotifyJiraTest.php.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Unit/NotifySlackTest.php
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/CheckNoExitSelfTest.php
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/NotifyEmailTest.php.vortex/tooling/tests/Unit/NotifySlackTest.php
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.vortex/tooling/tests/Unit/NotifyGithubTest.php
🧬 Code graph analysis (21)
.vortex/tooling/tests/Unit/GetenvTest.php (4)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (2)
getenv_default(119-136)getenv_required(161-182).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (5)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)mockRequestAssertAllMocksConsumed(458-469)mockRequestMultiple(229-359).vortex/tooling/src/helpers.php (3)
request_get(341-347)request_post(369-376)request(398-455)
.vortex/tooling/tests/Unit/TokenTest.php (1)
.vortex/tooling/src/helpers.php (1)
replace_tokens(296-312)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
__construct(22-31)getOutput(39-41)
.vortex/tooling/tests/Self/MockQuitSelfTest.php (5)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (1)
quit(476-478)
.vortex/tooling/tests/Unit/RequestTest.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(341-347)request_post(369-376)request(398-455)
.vortex/tooling/tests/Unit/OverrideTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (1)
execute_override(43-52).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockQuit(196-212)
.vortex/tooling/tests/Unit/DotenvTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (1)
load_dotenv(60-94)
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28)
.vortex/tooling/tests/Unit/FormatterTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (2)
fail(260-263)term_supports_color(268-270)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/tests/Unit/UnitTestCase.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
QuitErrorException(12-43)getOutput(39-41).vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
QuitSuccessException(12-43)getOutput(39-41).vortex/tooling/tests/Traits/MockTrait.php (2)
mockTearDown(63-81)mockQuit(196-212)
.vortex/tooling/tests/Unit/CommandExistsTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
command_exists(278-283)fail(260-263).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Unit/NotifyEmailTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120)
.vortex/tooling/tests/Unit/NotifyGithubTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (3)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
__construct(22-31)getOutput(39-41)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/tests/Unit/NotifyWebhookTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequest(373-389)
.vortex/tooling/tests/Unit/NotifySlackTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequestPost(436-450)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
🪛 Gitleaks (8.29.0)
.vortex/tooling/tests/Unit/NotifyGithubTest.php
[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php
[high] 26-26: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 49-49: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 74-74: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 92-92: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 101-101: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 119-119: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 145-145: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 167-167: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 184-184: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 201-201: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~230-~230: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
.vortex/tooling/plan.test-notify.md
[style] ~400-~400: To elevate your writing, try using a synonym here.
Context: ...t count per script - Any lines that are hard to cover (with explanation) - Overall c...
(HARD_TO)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/scripts_conversion_plan.md
104-104: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
179-179: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
190-190: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
200-200: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
211-211: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
222-222: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
233-233: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
333-333: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
338-338: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
.vortex/tooling/CLAUDE.md
104-104: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
179-179: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
190-190: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
200-200: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
211-211: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
222-222: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
233-233: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
333-333: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
338-338: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-docs
| /** | ||
| * Convert a multiline string into a JIRA ADF paragraph node. | ||
| * | ||
| * @param string $text | ||
| * Multiline text to convert. | ||
| * | ||
| * @return array | ||
| * ADF paragraph node. | ||
| */ | ||
| function text_to_adf(string $text): array { | ||
| $text = str_replace(["\r\n", "\r"], "\n", $text); | ||
| $lines = explode("\n", $text); | ||
|
|
||
| $content = []; | ||
| foreach ($lines as $index => $line) { | ||
| if ($index > 0) { | ||
| $content[] = ['type' => 'hardBreak']; | ||
| } | ||
| if ($line !== '') { | ||
| $content[] = ['type' => 'text', 'text' => $line]; | ||
| } | ||
| } | ||
|
|
||
| return ['type' => 'paragraph', 'content' => $content]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Fix ADF doc content shape: wrap paragraph node in an array
text_to_adf() returns a single paragraph node, but comment_body['content'] is set to that node directly, whereas the ADF doc schema expects content to be an array of top‑level nodes. This can break Jira’s validation of the comment payload.
Consider wrapping the paragraph node:
- $jira_message_adf = text_to_adf($jira_message);
-
- $comment_body = [
- 'type' => 'doc',
- 'version' => 1,
- 'content' => $jira_message_adf,
- ];
+ $jira_message_adf = text_to_adf($jira_message);
+
+ $comment_body = [
+ 'type' => 'doc',
+ 'version' => 1,
+ 'content' => [
+ $jira_message_adf,
+ ],
+ ];Please double‑check against the latest Jira ADF documentation and adjust any existing tests (e.g. NotifyJiraTest) that assert on the payload structure.
Also applies to: 153-159
I'll verify this review comment by checking the Jira ADF specification and examining the actual code and tests.
Now let me examine the actual code and tests in the repository:
Now let me examine the actual code in the repository:
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Find and examine the notify-jira file
find . -name "*notify-jira*" -o -name "*notify_jira*" 2>/dev/null | head -20Length of output: 108
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Examine the notify-jira file with line numbers, focusing on the target lines
cat -n ./.vortex/tooling/src/notify-jira | sed -n '70,105p'Length of output: 1246
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Examine the notify-jira file around lines 140-160 for comment_body construction
cat -n ./.vortex/tooling/src/notify-jira | sed -n '135,165p'Length of output: 971
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Search for other instances of text_to_adf usage or similar comment_body patterns
rg -n "text_to_adf|comment_body" ./.vortex/tooling/src/notify-jiraLength of output: 167
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Check full file length and search for other 'doc' type structures or transitions/assignee handling
wc -l ./.vortex/tooling/src/notify-jira
echo "---"
rg -n "'type'\s*=>\s*'doc'|'type'\s*=>\s*'paragraph'" ./.vortex/tooling/src/notify-jiraLength of output: 124
[/run_scripts]
🏁 Script executed:
#!/bin/bash
# Examine the test file for payload structure assertions
cat ./.vortex/tests/bats/unit/notify-jira.batsLength of output: 5485
Wrap ADF doc content in array: single paragraph node violates schema
The review comment is correct. ADF document structure requires content to be an array of block-level nodes.
Verification confirms the issue:
text_to_adf()(line 84) returns a single paragraph node:['type' => 'paragraph', 'content' => ...]comment_body(lines 155–159) assigns this node directly:'content' => $jira_message_adf- This violates ADF schema; JIRA's API expects
contentas an array of nodes
The suggested fix is correct—wrap the paragraph in an array:
- $comment_body = [
- 'type' => 'doc',
- 'version' => 1,
- 'content' => $jira_message_adf,
- ];
+ $comment_body = [
+ 'type' => 'doc',
+ 'version' => 1,
+ 'content' => [$jira_message_adf],
+ ];This is the only location in the file where this pattern occurs. The test file (notify-jira.bats) does not validate payload structure, so the issue would not be caught by existing tests. After applying the fix, verify with JIRA's API documentation or by testing the comment posting flow.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In .vortex/tooling/src/notify-jira around lines 75–99, text_to_adf() currently
returns a single paragraph node (['type'=>'paragraph','content'=>...]) which
causes the top-level ADF `content` to be a node instead of an array of block
nodes; change text_to_adf to return an array of block nodes (i.e., wrap the
paragraph node in an array) or, alternatively, wrap the paragraph node in an
array where it is assigned to comment_body so that the final ADF payload uses
'content' => [<paragraph node>], and adjust any callers if necessary.
| note('JIRA notification summary:'); | ||
| note('Project : ' . $notify_project); | ||
| note('Deployment : ' . $notify_label); | ||
| note('Issue : ' . $jira_issue); | ||
| note('Environment URL: ' . $notify_env_url); | ||
| note('Login URL : ' . $notify_login_url); | ||
| note('Endpoint : ' . $jira_endpoint); | ||
| note('User email : ' . $jira_user_email); | ||
| note('Transition : ' . ($jira_transition ?: '<none>')); | ||
| note('Assignee email : ' . ($jira_assignee_email ?: '<none>')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconsider logging JIRA user email in standard output
note('User email : ' . $jira_user_email); logs a concrete user identifier to whatever sink consumes these logs (CI, shared logs, etc.). Depending on your privacy/compliance posture, this may be considered sensitive PII.
If that’s a concern in your environment, consider removing the email from the summary or guarding it behind a verbose/debug flag.
🤖 Prompt for AI Agents
.vortex/tooling/src/notify-jira around lines 161 to 170: the summary currently
prints the JIRA user email which may expose PII; either remove the note('User
email : ' . $jira_user_email); line entirely or wrap it in a verbosity/debug
check (e.g., only log when a VERBOSE/DEBUG flag is set) or mask the email (show
only domain or initials) before logging so that sensitive user identifiers are
not emitted to standard output by default.
| if (!empty($jira_transition)) { | ||
| task('Discovering transition ID for %s', $jira_transition); | ||
| $url = sprintf('%s/rest/api/3/issue/%s/transitions', $jira_endpoint, $jira_issue); | ||
| $response = request_get($url, [ | ||
| 'Authorization: Basic ' . $token, | ||
| 'Content-Type: application/json', | ||
| ]); | ||
|
|
||
| $transition_id = NULL; | ||
| if ($response['ok'] && $response['body']) { | ||
| $payload = json_decode((string) $response['body'], TRUE); | ||
| if (isset($payload['transitions']) && is_array($payload['transitions'])) { | ||
| foreach ($payload['transitions'] as $t) { | ||
| if (isset($t['name']) && $t['name'] === $jira_transition) { | ||
| $transition_id = $t['id'] ?? NULL; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!$transition_id || !ctype_digit((string) $transition_id)) { | ||
| fail('Unable to retrieve transition ID'); | ||
| } | ||
|
|
||
| pass('Found transition ID %s', $transition_id); | ||
|
|
||
| task('Transitioning issue to ' . $jira_transition); | ||
|
|
||
| $payload_data = ['transition' => ['id' => $transition_id]]; | ||
| $url = sprintf('%s/rest/api/3/issue/%s/transitions', $jira_endpoint, $jira_issue); | ||
| request_post($url, json_encode($payload_data), [ | ||
| 'Authorization: Basic ' . $token, | ||
| 'Content-Type: application/json', | ||
| ]); | ||
|
|
||
| pass('Transitioned issue to %s ', $jira_transition); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Handle failures from transition and assignee update requests
Both the transition request (request_post at lines 220‑225) and the assignee update (request at lines 259‑266) ignore HTTP responses. If Jira returns a 4xx/5xx or malformed body, the script will still report success, which makes troubleshooting hard.
You could, for example:
- Capture and inspect the responses.
- Fail with a clear message if
$response['ok']is false or the body is missing/invalid.
This would align error handling for these steps with the stricter checks used for authentication and comment creation earlier in the script.
Also applies to: 230-269
| /** | ||
| * Self-tests for check-exit-usage.php script. | ||
| * | ||
| * These tests verify that the exit() usage checker works correctly. | ||
| */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align class docblock with actual script name
The docblock still references check-exit-usage.php while $this->scriptPath targets check-no-exit.php. Update the description to avoid confusion when tracking which tool these self-tests validate.
🤖 Prompt for AI Agents
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php lines 9-13: the class
docblock mentions check-exit-usage.php and "exit() usage checker" while the test
actually targets check-no-exit.php via $this->scriptPath; update the docblock
text to reference check-no-exit.php (and adjust the short description to say it
verifies the check-no-exit script) so the comment accurately reflects which tool
the self-tests validate.
| public function testSuccessfulNotificationPostDeployment(): void { | ||
| $this->mockRequestPost( | ||
| 'https://hooks.slack.com/services/T00/B00/XXXX', | ||
| $this->callback(function ($body): true { | ||
| /** @var array {username: string, icon_emoji: string, attachments: array} $payload */ | ||
| $payload = json_decode($body, TRUE); | ||
| // Verify payload structure. | ||
| $this->assertEquals('Deployment Bot', $payload['username']); | ||
| $this->assertEquals(':rocket:', $payload['icon_emoji']); | ||
| $this->assertEquals('good', $payload['attachments'][0]['color']); | ||
| $this->assertStringContainsString('Deployment Complete', $payload['attachments'][0]['title']); | ||
| $this->assertEquals('test-project', $this->getFieldValue($payload, 'Deployment')); | ||
| return TRUE; | ||
| }), | ||
| ['Content-Type: application/json'], | ||
| 10, | ||
| ['status' => 200, 'body' => 'ok'] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure mockRequestPost() actually uses the $body callback so payload assertions run
In this and other tests you pass a $this->callback(...) as the second argument to mockRequestPost() and assert heavily on the decoded JSON payload. However, the current MockTrait::mockRequestPost() implementation only forwards url, method and response to mockRequestMultiple:
protected function mockRequestPost(
string $url,
$body = NULL,
array $headers = [],
int $timeout = 10,
?array $response = NULL,
string $namespace = 'DrevOps\\VortexTooling',
): void {
$this->mockRequestMultiple([[
'url' => $url,
'method' => 'POST',
'response' => $response ?? [],
],
], $namespace);
}As a result, the $body callback you pass here is never consumed, and these tests do not currently verify the outbound Slack payload.
Consider updating the mocking layer so mockRequestPost() (and, if needed, mockRequestMultiple()) propagates and uses the body/headers/timeout information, e.g. by including them in the request definition passed into mockRequestMultiple and having the underlying stub invoke the callback on the actual body. That way, regressions in the payload shape will be caught by these tests as intended.
🤖 Prompt for AI Agents
In .vortex/tooling/tests/Unit/NotifySlackTest.php around lines 34 to 51 the test
passes a $this->callback(...) as the body argument to mockRequestPost() but the
MockTrait::mockRequestPost() currently ignores body/headers/timeout and only
sends url/method/response to mockRequestMultiple, so the assertions never run;
update mockRequestPost() to include the provided $body, $headers and $timeout in
the request array passed to mockRequestMultiple (e.g. add keys 'body',
'headers', 'timeout'), and then update mockRequestMultiple() (or its underlying
stub) to detect when a 'body' entry is a PHPUnit callback and invoke it with the
actual request payload string (and otherwise store/pass the body), and ensure
headers and timeout are forwarded to the stub so tests receive and can assert
against the real outbound payload.
40fadd2 to
4836323
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
♻️ Duplicate comments (38)
.vortex/tooling/src/notify-slack (1)
128-134: Optionally validate Slack response body contentThe current check only verifies HTTP 200 status. Slack webhooks can return 200 with error strings in the body (e.g.,
invalid_payload,no_service). Consider parsing and validating the response body matches the expected success indicator (typically"ok") for stricter error detection during misconfiguration or invalid payloads..vortex/tooling/scripts_conversion_plan.md (1)
84-166: Update helper function names to match actual implementationThe plan references
load_env(),execute_override_if_exists(),validate_variable(), andvalidate_command(), but the actual helpers.php implementation usesload_dotenv(),execute_override(),getenv_required(), andcommand_exists(). Update the examples throughout the document to use the correct function names.Also applies to: 196-261, 408-417
.vortex/tooling/phpcs.xml (2)
21-30: Simplify file specification by removing redundant entriesLines 22-28 individually list
src/notify-*subdirectories even though line 21 already includessrc. Since PHPCS scans directories recursively by default, these explicit entries are redundant and could be removed for cleaner configuration.
37-56: Fix exclude-pattern glob syntaxThe patterns use backslash-escaped dots (e.g.,
*.Test\.php) but PHPCS treats exclude-patterns as shell-style globs, not regex. The backslashes are unnecessary and may not match correctly. Use*Test.phpand*TestCase.phpinstead, or use directory-aware patterns like**/*Test.phpto match subdirectories..vortex/tooling/src/notify-email (4)
136-161: CRITICAL: Email header injection vulnerabilityLines 136-143 and 148-154 construct email headers using user-controlled values (
$email_from,$email_subject,$name,\nor\r\nin these environment variables.Required fix: Strip or reject CR/LF characters from all header values:
// Add before line 128: function sanitize_email_header(string $value): string { return str_replace(["\r", "\n", "\0"], '', $value); } // Then sanitize before use: $email_from = sanitize_email_header($email_from); $email_subject = sanitize_email_header($email_subject); // In the loop: $email = sanitize_email_header($email); $name = sanitize_email_header($name);
100-114: Reuse timestamp for consistencyThe subject and message each call
date()separately, which can result in timestamps differing by a second. Compute the timestamp once before both token replacements to ensure they're identical.
128-128: Filter empty recipient entriesExtra commas or whitespace in
VORTEX_NOTIFY_EMAIL_RECIPIENTScan produce empty strings afterexplode/trim, leading to blankTo:headers and unnecessary mail command invocations. Addarray_filterto remove empty entries after trimming.
148-160: Log failed email deliveriesOnly successful sends are tracked in
$sent. Failed deliveries are silent, making debugging difficult. Consider capturing and logging which recipients failed with the command exit code and output for troubleshooting..vortex/tooling/src/notify-jira (3)
155-159: CRITICAL: ADF content must be an array of nodesLine 158 assigns
$jira_message_adf(a single paragraph node) directly tocontent, but the ADF document schema requirescontentto be an array of block-level nodes. This violates Jira's ADF specification and may cause API validation errors.Fix: Wrap the paragraph node in an array:
$comment_body = [ 'type' => 'doc', 'version' => 1, - 'content' => $jira_message_adf, + 'content' => [$jira_message_adf], ];
168-168: Consider PII implications of logging user emailLogging
$jira_user_emailto standard output may expose personally identifiable information in CI logs or shared environments. Consider removing this log line, masking the email (e.g., showing only the domain), or guarding it behind a debug/verbose flag if compliance is a concern.
218-228: Add error handling for transition API requestThe transition request at lines 222-225 doesn't validate the response. If Jira returns an error (4xx/5xx), the script reports success anyway. Consider capturing the response and failing with a clear message if
$response['ok']is false.Also applies to: 255-269
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (4)
9-13: Update class docblock to reference correct script name.The docblock mentions "check-exit-usage.php" but the test actually targets "check-no-exit.php" (line 31). Update the description to avoid confusion.
29-36: Use unique temporary directories per test to prevent failures.The setUp() creates a fixed
$testDirpath, causing two critical issues:
- mkdir() warnings fail tests: Subsequent tests attempt to mkdir existing directories, triggering PHP warnings that fail tests (given
phpunit.xml'sfailOnWarning="true").- File persistence breaks isolation: Earlier tests' PHP files persist in the shared directory, making
testHandlesEmptyDirectories()find unexpected files and become order-dependent.Apply this fix:
protected function setUp(): void { parent::setUp(); $this->scriptPath = dirname(__DIR__, 2) . '/check-no-exit.php'; - $this->testDir = self::$tmp . '/check-exit-test'; - mkdir($this->testDir); - mkdir($this->testDir . '/src'); - mkdir($this->testDir . '/tests'); + $this->testDir = self::$tmp . '/check-exit-test-' . uniqid('', true); + mkdir($this->testDir . '/src', 0777, true); + mkdir($this->testDir . '/tests', 0777, true); }This creates an isolated directory per test and uses recursive mkdir to avoid warnings.
47-50: Extract and escape shell command execution to a helper method.The inline
exec()calls are duplicated across six tests and don't escape$this->testDiror$this->scriptPath, which could break with paths containing spaces or special characters.Add a helper method:
/** * Run check-no-exit.php from the current test directory. * * @return array{0: string[], 1: int} */ private function runCheckNoExitScript(): array { $output = []; $exit_code = 0; $cmd = sprintf( 'cd %s && php %s 2>&1', escapeshellarg($this->testDir), escapeshellarg($this->scriptPath), ); exec($cmd, $output, $exit_code); return [$output, $exit_code]; }Then replace each
exec()block with:[$output, $exit_code] = $this->runCheckNoExitScript();Also applies to: 65-68, 82-84, 103-105, 120-122, 133-135
113-126: Assert exit code in line-number test for consistency.
testReportsCorrectLineNumbers()only verifies the reported filename:line but doesn't assert that the script exits with a non-zero code. If the script ever started logging correctly but mistakenly exited 0, this test would still pass.Add the assertion:
$output_str = implode("\n", $output); + $this->assertNotEquals(0, $exit_code, 'Script should exit with non-zero code when exit() is detected'); $this->assertStringContainsString('line-test.php:5', $output_str);.vortex/tooling/src/helpers.php (4)
43-52: Add path traversal protection to execute_override.The
$nameparameter is concatenated directly into a file path without validation. While current usage passesbasename(__FILE__), future callers might not, creating a path traversal vulnerability.Add validation before path construction:
function execute_override(string $name): void { + // Prevent path traversal by ensuring name doesn't contain separators. + if (str_contains($name, DIRECTORY_SEPARATOR) || str_contains($name, '/') || str_contains($name, '\\')) { + fail('Invalid script name for override: %s', $name); + } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); if ($dir) { $path = $dir . DIRECTORY_SEPARATOR . $name;
278-283: Shell-escape the command argument to prevent injection.
command_exists()interpolates$commanddirectly into a shell string. If$commandever comes from untrusted input, this becomes a command-injection vector.Apply this fix:
function command_exists(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }
404-404: Check curl_init return value before using the handle.
curl_init()can returnfalseon failure. Passingfalsetocurl_setopt_array()will cause a TypeError.Add error handling:
$ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + } try {
426-426: Add error handling for curl_setopt_array failure.While the missing
curl_initcheck is already flagged, there's also no verification thatcurl_setopt_array()succeeded. If setting options fails, the request proceeds with incorrect configuration.Add a check after the call:
curl_setopt_array($ch, $opts); + if (curl_errno($ch)) { + fail('Failed to set curl options: %s', curl_error($ch)); + }Note: This is separate from the
curl_initcheck and should also be addressed..vortex/tooling/tests/Unit/TokenTest.php (1)
32-55: Consider adding test cases for JSON-escaping behavior.Since
replace_tokens()escapes values viajson_encode()before substitution, it would be useful to add data-provider scenarios where replacement values contain quotes, backslashes, or newlines to verify that escaping behavior. For example:'value with quotes' => [ 'template' => 'Message: %msg%', 'replacements' => ['msg' => 'He said "hello"'], 'expected' => 'Message: He said \"hello\"', ], 'value with newlines' => [ 'template' => 'Text: %text%', 'replacements' => ['text' => "Line1\nLine2"], 'expected' => 'Text: Line1\nLine2', ],This would make regressions in the escaping logic easier to catch.
.vortex/tooling/src/notify (1)
66-66: Use DIRECTORY_SEPARATOR for cross-platform compatibility.The script path is constructed with a hardcoded forward slash, which may not work correctly on Windows systems.
Apply this fix:
- $script = __DIR__ . ('/notify-' . $channel); + $script = __DIR__ . DIRECTORY_SEPARATOR . 'notify-' . $channel;.vortex/tooling/tests/Unit/NotifyGithubTest.php (1)
23-31: Use a clearly fake token value to avoid Gitleaks detection.The hard-coded test token
'ghp_test123456'matches GitHub's personal access token prefix and is flagged by Gitleaks as a potential secret. Even though this is obviously a test value, it still fails secret scanning.Change it to a format that doesn't resemble a real token:
$this->envSetMultiple([ - 'VORTEX_NOTIFY_GITHUB_TOKEN' => 'ghp_test123456', + 'VORTEX_NOTIFY_GITHUB_TOKEN' => 'test-token-value', 'VORTEX_NOTIFY_GITHUB_REPOSITORY' => 'owner/repo',Alternatives include
'TEST_TOKEN','fake-github-token', or concatenating strings like'ghp_' . 'TEST_' . 'TOKEN'to break the pattern..vortex/tooling/tests/Unit/GetenvTest.php (1)
137-146: Fix unreachable output assertions in error tests.These tests use
captureOutput()together withexpectException(), butcaptureOutput()rethrows exceptions (line 187), so$outputis never assigned and the subsequentassertStringContainsString(..., $output)lines are unreachable. The tests therefore don't actually verify the error message text.Refactor to manually catch the exception:
public function testGetenvRequiredWithNoVarsSetFails(): void { $this->mockQuit(1); try { $output = $this->captureOutput(function (): void { \DrevOps\VortexTooling\getenv_required('UNSET_VAR_1', 'UNSET_VAR_2'); }); $this->fail('Expected QuitErrorException was not thrown'); } catch (QuitErrorException $e) { $output = $e->getOutput(); $this->assertStringContainsString('Missing required value for UNSET_VAR_1, UNSET_VAR_2', $output); } }Apply similar changes to
testGetenvRequiredWithAllEmptyStringsFails()andtestGetenvRequiredWithSingleVarNotSetFails().Also applies to: 148-160, 162-171
.vortex/tooling/src/notify-webhook (1)
16-17: Remove blank lines after separator comments to fix PHPCS warnings.The pipeline flags separator comments for having blank lines immediately after them. Remove these blank lines to satisfy
Drupal.Commenting.InlineComment.SpacingAfter:// ----------------------------------------------------------------------------- - // Webhook notification project name.// ----------------------------------------------------------------------------- - info('Started Webhook notification.');Based on learnings, this coding standard is enforced by the project's PHPCS configuration.
Also applies to: 63-64
.vortex/tooling/CLAUDE.md (1)
104-104: Address markdown linting violations to keep CI green.The markdownlint tool flags several formatting issues that are straightforward to fix:
- Fenced code blocks need blank lines before and after them (lines 104, 155, 333, 338)
- Headings need blank lines before and after them (lines 179, 190, 200, 211, 222, 233)
Also applies to: 155-155, 179-179, 190-190, 200-200, 211-211, 222-222, 233-233, 333-333, 338-338
.vortex/tooling/src/notify-github (2)
56-66: Consider removing the upper length bound for future-proofing.The current validation enforces a maximum of 11 digits for deployment IDs. While this matches current GitHub IDs, it may reject future larger IDs if GitHub changes their format.
As noted in a previous review, keeping the numeric check (
ctype_digit()) but removing the upper bound would make the validation more resilient.Apply this diff to relax the constraint:
function deployment_id_is_valid(mixed $deployment_id): bool { if (empty($deployment_id)) { return FALSE; } $id_str = (string) $deployment_id; $length = strlen($id_str); - // Check length (9-11 digits) and numeric format. - return $length >= 9 && $length <= 11 && ctype_digit($id_str); + // Check minimum length (9 digits) and numeric format. + return $length >= 9 && ctype_digit($id_str); }
84-88: Add explicit Content-Type header for JSON POST requests.The deployment creation sends a JSON body but doesn't specify
Content-Type: application/json. While cURL may infer this, being explicit prevents potential issues with the GitHub API.The same issue appears in the status update POST at lines 146-150.
Apply this diff:
$response = request_post($url, json_encode($payload_data), [ 'Authorization: token ' . $github_token, 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]);.vortex/tooling/tests/Unit/RequestTest.php (1)
30-100: Network-dependent tests remain in unit test suite.These tests still make real HTTP calls to example.com, which makes them fragile and environment-dependent. While marked as addressed in a past review (commit ef08bb3), the actual network calls remain in the code.
Consider either:
- Moving these to a separate integration test group that can be skipped in CI
- Replacing with mocked tests using MockTrait as suggested previously
- Keeping minimal integration checks but ensuring the default unit run doesn't require network access
The tests rely on:
- Network availability
- DNS resolution
- example.com's response behavior (status codes, timing)
This can cause non-deterministic failures in restricted environments.
.vortex/tooling/tests/Unit/OverrideTest.php (4)
26-32: Replace direct putenv() with EnvTrait helper.Direct
putenv()bypasses EnvTrait's automatic cleanup intearDown(), potentially affecting other tests.Apply this diff:
public function testExecuteOverrideNoCustomDir(): void { - putenv('VORTEX_TOOLING_CUSTOM_DIR'); + $this->envUnset('VORTEX_TOOLING_CUSTOM_DIR'); $this->expectNotToPerformAssertions();Based on learnings
34-42: Replace direct putenv() with EnvTrait helper.Use
$this->envSet()for consistent environment management and automatic cleanup.Apply this diff:
public function testExecuteOverrideCustomDirNoFile(): void { $custom_dir = self::$tmp . '/custom'; mkdir($custom_dir, 0777, TRUE); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);Based on learnings
44-55: Replace direct putenv() with EnvTrait helper.Use
$this->envSet()instead ofputenv()for proper test isolation.Apply this diff:
chmod($script_path, 0644); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);Based on learnings
57-84: Replace direct putenv() with EnvTrait helper.Use
$this->envSet()for environment variable management.Apply this diff:
chmod($script_path, 0755); - putenv('VORTEX_TOOLING_CUSTOM_DIR=' . $custom_dir); + $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);Based on learnings
.vortex/tooling/tests/Unit/FormatterTest.php (3)
33-111: Replace direct putenv() with EnvTrait methods.Multiple test methods use
putenv()directly to set or unset theTERMenvironment variable (lines 35, 64, 103, 106). This bypasses EnvTrait's automatic cleanup and can affect other tests.Apply this diff:
#[DataProvider('dataProviderOutputFormatters')] public function testOutputFormatters(string $function, string $expected_prefix): void { - putenv('TERM=dumb'); + $this->envSet('TERM', 'dumb'); ob_start(); @@ #[DataProvider('dataProviderOutputFormattersWithColor')] public function testOutputFormattersWithColor(string $function, string $expected_prefix): void { - putenv('TERM=xterm-256color'); + $this->envSet('TERM', 'xterm-256color'); ob_start(); @@ #[DataProvider('dataProviderTermSupportsColor')] public function testTermSupportsColor(string|bool $term_value, bool $expected): void { if ($term_value === FALSE) { - putenv('TERM'); + $this->envUnset('TERM'); } else { - putenv('TERM=' . $term_value); + $this->envSet('TERM', $term_value); }Based on learnings
77-82: Add missing fail_no_exit test case for colored output.The
dataProviderOutputFormattersWithColoris missing coverage forfail_no_exit, which supports color output. This results in incomplete test coverage.Apply this diff:
public static function dataProviderOutputFormattersWithColor(): array { return [ 'task' => ['task', '[TASK]'], 'info' => ['info', '[INFO]'], 'pass' => ['pass', '[ OK ]'], + 'fail_no_exit' => ['fail_no_exit', '[FAIL]'], ]; }
113-117: Add positive test case for color support detection.The data provider only tests scenarios where color support should return
FALSE. Add a test case to verify thatterm_supports_color()returnsTRUEfor color-capable terminals.Apply this diff:
public static function dataProviderTermSupportsColor(): array { return [ 'dumb terminal' => ['term_value' => 'dumb', 'expected' => FALSE], 'no terminal' => ['term_value' => FALSE, 'expected' => FALSE], + 'color terminal' => ['term_value' => 'xterm-256color', 'expected' => TRUE], ]; }.vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
61-107: Tests should userunScriptEarlyPass/runScriptErrorto validate both exit behavior and outputThe past review correctly identified that assertions on
$outputnever execute whenexpectException()is used withrunScript(). Once the mockedquit()throws the expected exception, PHPUnit stops test execution—assignments and assertions after the call are unreachable.Although this issue was marked as addressed in commit 2cde55e, the current code still follows the broken pattern. Use the helper methods provided in
UnitTestCase:
- For
testMockQuitScript0Successand similar success cases:$this->runScriptEarlyPass('tests/Fixtures/test-quit-passing', 'Script will exit with code 0');- For
testMockQuitScript0Failureand similar failure cases: wrap in a try-catch expectingRuntimeException, or refactor to match the working pattern inNotifyEmailTest.php.Apply this pattern (example for lines 61–71):
public function testMockQuitScript0Success(): void { $this->mockQuit(); - $this->expectException(QuitSuccessException::class); - $this->expectExceptionCode(0); - - $output = $this->runScript('tests/Fixtures/test-quit-passing'); - - $this->assertStringContainsString('Script will exit with code 0', $output); - $this->assertStringNotContainsString('ERROR Script continued after quit()', $output); + $this->runScriptEarlyPass( + 'tests/Fixtures/test-quit-passing', + 'Script will exit with code 0' + ); }Repeat for the other three script-based tests (lines 73–83, 85–95, 97–107).
.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
21-37: Move helper loading tosetUp()to ensure test isolationThe past review correctly identified that
helpers.phpis only loaded intestMockRequestGetSuccess(). All subsequent tests depend on this side-effect, which breaks test isolation. Running any later test individually (or with randomized order) will fail with "undefined function" errors.Move the require to a shared setup method:
+ protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockRequestGetSuccess(): void { $this->mockRequestGet( 'https://example.com/api', [], 10, ['status' => 200, 'body' => 'success response'] ); - // Load helpers.php AFTER setting up mocks. - require_once __DIR__ . '/../../src/helpers.php'; - $result = \DrevOps\VortexTooling\request_get('https://example.com/api');Note: The past review also identified PHPCS violations for fully-qualified names throughout this file and provided a complete patch. That issue remains unaddressed.
.vortex/tooling/tests/Unit/NotifyJiraTest.php (1)
200-331: Error‑path tests are structured well but inherit therunScriptError()syntax issueThe various failure tests (
testPreDeploymentEventSkipped,testAuthenticationFailure,testCommentCreationFailure,testTransitionDiscoveryFailure,testAssigneeDiscoveryFailure,testMissingRequiredVariables, and the data‑provider–driven failure tests at Lines 200–331, 364–378, 578–599, 601–633, 635–680, 682–727) consistently userunScriptEarlyPass()/runScriptError()with expected output substrings, which is a good pattern.However, as noted from the
UnitTestCase::runScriptError()snippet, there is a syntax error in the assertion (the $output) that would prevent any of these tests from running. Once that helper is fixed as suggested earlier, this suite’s error‑path coverage should behave as designed.Also applies to: 364-378, 578-599, 601-633, 635-680, 682-727
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (34)
.vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/plan.test-notify.md(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Self/CheckNoExitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Unit/CommandExistsTest.php(1 hunks).vortex/tooling/tests/Unit/DotenvTest.php(1 hunks).vortex/tooling/tests/Unit/FormatterTest.php(1 hunks).vortex/tooling/tests/Unit/GetenvTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyEmailTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyGithubTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyJiraTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyNewrelicTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyRouterTest.php(1 hunks).vortex/tooling/tests/Unit/NotifySlackTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyWebhookTest.php(1 hunks).vortex/tooling/tests/Unit/OverrideTest.php(1 hunks).vortex/tooling/tests/Unit/RequestTest.php(1 hunks).vortex/tooling/tests/Unit/TokenTest.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Unit/DotenvTest.php.vortex/tooling/tests/Unit/NotifySlackTest.php.vortex/tooling/tests/Unit/CommandExistsTest.php.vortex/tooling/tests/Unit/UnitTestCase.php.vortex/tooling/tests/Unit/FormatterTest.php.vortex/tooling/tests/Self/CheckNoExitSelfTest.php.vortex/tooling/tests/Unit/GetenvTest.php.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Unit/NotifyEmailTest.php.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Unit/TokenTest.php.vortex/tooling/tests/Unit/NotifyJiraTest.php
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/CheckNoExitSelfTest.php
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/NotifyEmailTest.php
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.vortex/tooling/tests/Unit/NotifyGithubTest.php
🧬 Code graph analysis (21)
.vortex/tooling/tests/Unit/OverrideTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (1)
execute_override(43-52).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockQuit(196-212)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (5)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)mockRequestAssertAllMocksConsumed(458-469)mockRequestMultiple(229-359).vortex/tooling/src/helpers.php (3)
request_get(341-347)request_post(369-376)request(398-455)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
__construct(22-31)getOutput(39-41).vortex/installer/src/Utils/Tui.php (1)
output(38-43)
.vortex/tooling/tests/Unit/DotenvTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (1)
load_dotenv(60-94)
.vortex/tooling/tests/Unit/NotifyWebhookTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequest(373-389)
.vortex/tooling/tests/Unit/NotifySlackTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequestPost(436-450)
.vortex/tooling/tests/Unit/CommandExistsTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
command_exists(278-283)fail(260-263).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Unit/UnitTestCase.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
QuitErrorException(12-43)getOutput(39-41).vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
QuitSuccessException(12-43)getOutput(39-41).vortex/tooling/tests/Traits/MockTrait.php (2)
mockTearDown(63-81)mockQuit(196-212)
.vortex/tooling/tests/Unit/FormatterTest.php (4)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (2)
fail(260-263)term_supports_color(268-270)
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28)
.vortex/tooling/tests/Unit/GetenvTest.php (4)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (2)
getenv_default(119-136)getenv_required(161-182).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Unit/NotifyEmailTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120)
.vortex/tooling/tests/Unit/RequestTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (3)
request_get(341-347)request_post(369-376)request(398-455)
.vortex/tooling/tests/Unit/NotifyRouterTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScriptEarlyPass(94-106)runScriptError(108-120)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (1)
mockPassthru(161-163)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
__construct(22-31)getOutput(39-41).vortex/installer/src/Utils/Tui.php (1)
output(38-43)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/tests/Unit/NotifyGithubTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/tests/Unit/TokenTest.php (1)
.vortex/tooling/src/helpers.php (1)
replace_tokens(296-312)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (3)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
🪛 Gitleaks (8.29.0)
.vortex/tooling/tests/Unit/NotifyGithubTest.php
[high] 24-24: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php
[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 48-48: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 73-73: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 91-91: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 100-100: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 118-118: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 144-144: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 166-166: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 183-183: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 200-200: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~230-~230: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
.vortex/tooling/plan.test-notify.md
[style] ~400-~400: To elevate your writing, try using a synonym here.
Context: ...t count per script - Any lines that are hard to cover (with explanation) - Overall c...
(HARD_TO)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/CLAUDE.md
104-104: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
155-155: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
179-179: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
190-190: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
200-200: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
211-211: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
222-222: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
233-233: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
333-333: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
338-338: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-docs
🔇 Additional comments (22)
.vortex/tooling/composer.json (1)
1-67: LGTM! Well-structured tooling manifestThe composer.json is properly configured for an internal tooling package with appropriate dev dependencies, autoloading, and helper scripts. The PHP constraint placement in
require-devis appropriate for this use case..vortex/tooling/src/notify-slack (1)
1-138: Slack notification script looks solidThe implementation correctly handles configuration, builds proper Slack payloads, manages token replacement, and provides good logging throughout. The PHPCS issues from past reviews have been addressed.
.vortex/tooling/scripts_conversion_plan.md (1)
1-574: Comprehensive conversion plan for Bash to PHP migrationThis planning document provides a well-structured approach to converting Vortex scripts from Bash to PHP, with clear phases, patterns, and success criteria.
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
1-43: LGTM! Well-designed test exceptionThe QuitErrorException is properly implemented with appropriate validation to ensure it only accepts non-zero exit codes. The symmetry with QuitSuccessException and the captured output feature make this suitable for testing termination scenarios.
.vortex/tooling/phpcs.xml (1)
1-57: PHPCS configuration properly customizes Drupal standardThe configuration appropriately extends Drupal coding standards and adds DrevOps-specific rules for the tooling codebase, with sensible exclusions for test files.
.vortex/tooling/plan.test-notify.md (1)
1-449: Comprehensive and well-structured test planThis testing plan provides clear objectives, scope, strategy, and implementation phases for achieving high test coverage of the notify scripts. The phased approach and detailed examples will guide effective test implementation.
.vortex/tooling/src/notify-jira (2)
103-116: Good: Proper early exit when issue not foundThe script correctly calls
quit()after logging the skip message when the deployment label doesn't contain an issue number, preventing undefined variable errors downstream.
1-271: Well-implemented Jira integration with comprehensive featuresThe notify-jira script properly handles authentication, comment posting with ADF formatting, optional transitions, and optional assignee updates. Past issues with unused functions and inconsistent output have been addressed.
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
22-41: LGTM!The exception class correctly enforces that only exit code 0 can be used, maintaining symmetry with QuitErrorException (which enforces non-zero codes). The validation and output capture are properly implemented.
.vortex/tooling/tests/Unit/CommandExistsTest.php (1)
26-48: LGTM!The tests correctly validate both successful and failed command existence checks. The error path properly captures output and verifies the exception and error message.
.vortex/tooling/tests/Unit/DotenvTest.php (1)
26-97: LGTM!The test suite provides comprehensive coverage of dotenv loading behavior, including edge cases like quoted values, values with equals signs, spaces, comments, and missing files. The data-driven approach ensures thorough validation.
.vortex/tooling/tests/Unit/NotifyEmailTest.php (1)
1-223: LGTM! Tests follow the correct pattern for script execution with early exitsThe tests properly use
runScriptEarlyPass()andrunScriptError()to validate both exit behavior and output. The data-driven approach for missing variables and the comprehensive coverage of email notification scenarios (sendmail, mail command, multiple recipients, custom messages, fallback variables, token replacement) demonstrate thorough testing..vortex/tooling/tests/Unit/NotifyWebhookTest.php (1)
1-223: LGTM! Comprehensive webhook notification test coverageThe tests properly handle script execution with early exits using
runScriptEarlyPass()andrunScriptError(). The coverage is thorough: default/custom payloads, pre-deployment handling, data-driven missing variables, fallback variables, HTTP methods, headers, status codes, error handling, token replacement, and URL sanitization. Well structured..vortex/tooling/tests/Unit/UnitTestCase.php (1)
1-122: LGTM! Well-designed test base class with proper exception and output handlingThe
runScript()family of methods correctly handles the complexity of testing scripts that callquit():
runScript()properly manages output buffering and working directory, catching Quit exceptions to attach output before rethrowingrunScriptEarlyPass()andrunScriptError()provide clean APIs for tests that need to validate both exit code and output- The exception-then-rethrow pattern ensures PHPUnit's
expectExceptionmechanism works while preserving output for assertionsThe composition of
MockTraitandEnvTraitprovides a solid foundation for the notify script tests..vortex/tooling/tests/Unit/NotifySlackTest.php (1)
1-263: Tests follow correct pattern; note that payload assertions require MockTrait enhancementThe test structure is correct—using
runScriptError()for failure cases and properly organizing test methods. The comprehensive coverage of Slack notification scenarios (post/pre-deployment, custom messages, channels, bot settings, missing variables, fallback, URL sanitization, token replacement) is excellent.As noted in the past review,
mockRequestPost()currently ignores the$bodycallback parameter (lines 34–46, 63–72, etc.), so payload assertions inside those callbacks won't execute. The past review provided guidance on updatingMockTrait::mockRequestPost()to support body validation. Until that's implemented, these tests verify the HTTP call is made but don't validate the payload structure.Based on past review feedback, this limitation should be addressed separately.
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (1)
1-254: LGTM! Comprehensive New Relic deployment notification testsThe tests properly use
runScriptEarlyPass()andrunScriptError()helpers to validate both exit behavior and output. Coverage is thorough: provided/auto-discovered App IDs, auto-generated revisions, custom descriptions/changelogs/endpoints, pre-deployment skipping, failure handling, and data-driven missing variables with fallback.Note: Gitleaks flags the test API keys (lines 25, 48, 73, etc.) as potential secrets. These are clearly test fixtures (
NRAK-TEST123456,NRAK-FALLBACK456) with no production value. The warnings can be safely ignored..vortex/tooling/tests/Self/MockPassthruSelfTest.php (1)
1-415: LGTM! Thorough passthru() mocking test coverageThe tests correctly validate
mockPassthru()behavior across multiple scenarios:
- Direct passthru calls with proper output buffering (lines 21–160)
- Argument validation and error conditions (lines 72–126)
- Multiple sequential mocks with consumption checks (lines 128–237)
- Script-driven validations using fixtures (lines 239–414)
The manual triggering of
mockPassthruAssertAllMocksConsumed()in under-consumption tests (lines 236, 398) is appropriate. Output buffering management with try-finally blocks ensures cleanup even when exceptions are expected. Well structured tests..vortex/tooling/tests/Unit/NotifyRouterTest.php (3)
16-33: Setup and baseline env configuration look solidUsing
setUp()(Line 20) to seedVORTEX_NOTIFY_PROJECT,VORTEX_NOTIFY_LABEL, andVORTEX_NOTIFY_ENVIRONMENT_URLgives all tests a consistent baseline and keeps individual tests focused on deltas only. No issues here.
30-46: Early‑exit skip/missing‑label tests are correctly delegated to shared helpers
testSkipWhenNotifySkipIsSet()andtestSkipWhenNoChannelsSpecified()(Lines 30–40), plustestFailureWhenLabelIsMissing()(Lines 42–46), userunScriptEarlyPass()/runScriptError()with expected output strings, which centralizes the exit‑code and output assertions and keeps tests concise. This pattern is clear and consistent.
64-85: Channel existence/permissions and per‑channel execution behavior are well coveredThe tests for a missing channel script (Lines 64–68), non‑executable script (Lines 70–85), failing channel exit code (Lines 87–97), and “all channels executed even if one has no output” (Lines 266–288) together exercise the main unhappy paths and aggregation behavior of the router. The use of a temporary file and
finallycleanup intestFailureWhenChannelScriptNotExecutable()is particularly good practice.Also applies to: 87-97, 266-288
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
20-33: Jira notifier setup is comprehensive and keeps tests focusedThe
setUp()method (Lines 20–33) initializes all Jira‑specific environment variables (project, user, token, label, URLs, event, endpoint), so individual tests only need to override what they care about. This keeps the suite readable and reduces duplication.
380-455: Fallbacks, default endpoint, full workflow, and issue‑format coverage are strongThe tests for generic env fallbacks, issue extraction from labels, default Jira endpoint, complete workflow with all features, event defaulting, and multiple issue formats (Lines 380–455, 457–487, 489–570, 729–758, 760–801) together provide thorough coverage of key behaviors and edge cases. The use of data providers for issue formats and clear output assertions (project, deployment, issue, URLs) is particularly good.
Also applies to: 457-487, 489-570, 729-758, 760-801
| public function testSuccessfulNotificationWithCustomMessage(): void { | ||
| $this->envSet('VORTEX_NOTIFY_JIRA_MESSAGE', 'Custom deployment of %project% to %label% at %timestamp%'); | ||
|
|
||
| // Mock authentication. | ||
| $this->mockRequestGet( | ||
| 'https://jira.example.com/rest/api/3/myself', | ||
| [ | ||
| 'Authorization: Basic ' . base64_encode('[email protected]:test-token-123'), | ||
| 'Content-Type: application/json', | ||
| ], | ||
| 10, | ||
| ['status' => 200, 'body' => '{"accountId": "123456789012345678901234"}'] | ||
| ); | ||
|
|
||
| // Mock comment creation. | ||
| $this->mockRequestPost( | ||
| 'https://jira.example.com/rest/api/3/issue/TEST-123/comment', | ||
| $this->callback(fn(): true => TRUE), | ||
| [ | ||
| 'Authorization: Basic ' . base64_encode('[email protected]:test-token-123'), | ||
| 'Content-Type: application/json', | ||
| ], | ||
| 10, | ||
| ['status' => 201, 'body' => '{"id": "10001"}'] | ||
| ); | ||
|
|
||
| $output = $this->runScript('src/notify-jira'); | ||
|
|
||
| $this->assertStringContainsString('Finished JIRA notification', $output); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Custom‑message test doesn’t currently verify the rendered message
testSuccessfulNotificationWithCustomMessage() (Lines 333–362) sets VORTEX_NOTIFY_JIRA_MESSAGE to a templated string and only asserts that the notification finished successfully. It doesn’t check that the custom message was actually used in the comment body or output.
If the notifier logs the rendered message or includes it in the request payload (which you’re already mocking), consider asserting on that so regressions in message templating are detectable.
| $this->mockRequestGet( | ||
| 'https://jira.example.com/rest/api/3/myself', | ||
| [ | ||
| 'Authorization: Basic ' . base64_encode('[email protected]:test-token-123'), | ||
| 'Content-Type: application/json', | ||
| ], | ||
| 10, | ||
| ['status' => 200, 'body' => '{"accountId": "123456789012345678901234"}'] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Optional: factor out repeated headers/authorization into small helpers
Across many mocks you repeat the same Authorization + JSON headers and status/ID patterns (e.g., Lines 337–345, 393–414, 460–481, 493–514, 517–537, 540–562, 603–611, 649–658, 687–705, 733–752, 764–783). Extracting a tiny helper (e.g., private function authHeaders(): array and maybe builders for common 200/201/204 responses) would reduce duplication and make it easier to tweak these values in one place if needed.
Not mandatory, but would improve maintainability.
Also applies to: 393-414, 460-481, 493-514, 517-537, 540-562, 603-611, 649-658, 687-705, 733-752, 764-783
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyJiraTest.php around lines 337-345: the test
file repeats identical Authorization and Content-Type headers and common
response payloads across many mocks; factor this duplication by adding a private
helper (e.g., private function authHeaders(): array) that returns the standard
Authorization + JSON headers and small helper builders for common responses
(e.g., okResponse(array $body), createdResponse(array $body),
noContentResponse()) and then replace the repeated literal header/response
arrays in each mock block (including the other listed ranges) with calls to
these helpers so tests remain identical but duplication is eliminated and easy
to change.
| #[DataProvider('dataProviderFailureWithInvalidEventType')] | ||
| public function testFailureWithInvalidEventType(string $event): void { | ||
| $this->envSet('VORTEX_NOTIFY_EVENT', $event); | ||
|
|
||
| $this->runScriptError('src/notify', 'Unsupported event ' . $event . ' provided'); | ||
| } | ||
|
|
||
| public static function dataProviderFailureWithInvalidEventType(): array { | ||
| return [ | ||
| 'invalid event' => ['invalid_event'], | ||
| 'deployment' => ['deployment'], | ||
| 'pre-deployment' => ['pre-deployment'], | ||
| 'postdeployment' => ['postdeployment'], | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Event type coverage is good, but the “default is post_deployment” test is a bit weak
The invalid/valid event type data providers and tests (Lines 48–62, 194–217) cover a good range of supported and unsupported values. However, testDefaultEventTypeIsPostDeployment() (Lines 219–232) only asserts that notifications complete successfully; it doesn’t actually verify that the default event used is post_deployment vs. some other non‑skipping value.
If the router logs or exposes the resolved event, consider asserting on that specific text/value so the test fails if the default ever changes.
Also applies to: 194-217, 219-232
🤖 Prompt for AI Agents
In .vortex/tooling/tests/Unit/NotifyRouterTest.php around lines 48–62 (and also
update the related test at lines ~219–232), the default-event test only asserts
success but not that the resolved event is post_deployment; update the test to
explicitly assert that the router used the 'post_deployment' event by capturing
the script/router output or log and asserting that it contains the literal
'post_deployment' (or the exact log message that names the resolved event). If
necessary adjust the helper (e.g. runScript/runScriptError) to return
stdout/stderr so you can assert the presence of 'post_deployment', and add a
targeted assertion after the success check so the test will fail if the default
event changes.
| public function testSuccessfulNotificationWithDefaultChannel(): void { | ||
| $this->envUnset('VORTEX_NOTIFY_CHANNELS'); | ||
|
|
||
| $script_path = realpath(__DIR__ . '/../../src/notify-email'); | ||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $script_path . '"', | ||
| 'output' => 'Email notification sent successfully', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify'); | ||
|
|
||
| $this->assertStringContainsString('Started dispatching notifications', $output); | ||
| $this->assertStringContainsString('Email notification sent successfully', $output); | ||
| $this->assertStringContainsString('Finished dispatching notifications', $output); | ||
| } | ||
|
|
||
| public function testSuccessfulNotificationWithSingleChannel(): void { | ||
| $this->envSet('VORTEX_NOTIFY_CHANNELS', 'slack'); | ||
|
|
||
| $script_path = realpath(__DIR__ . '/../../src/notify-slack'); | ||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $script_path . '"', | ||
| 'output' => 'Slack notification sent successfully', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify'); | ||
|
|
||
| $this->assertStringContainsString('Started dispatching notifications', $output); | ||
| $this->assertStringContainsString('Slack notification sent successfully', $output); | ||
| $this->assertStringContainsString('Finished dispatching notifications', $output); | ||
| } | ||
|
|
||
| public function testSuccessfulNotificationWithMultipleChannels(): void { | ||
| $this->envSet('VORTEX_NOTIFY_CHANNELS', 'email,slack,webhook'); | ||
|
|
||
| $email_path = realpath(__DIR__ . '/../../src/notify-email'); | ||
| $slack_path = realpath(__DIR__ . '/../../src/notify-slack'); | ||
| $webhook_path = realpath(__DIR__ . '/../../src/notify-webhook'); | ||
|
|
||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $email_path . '"', | ||
| 'output' => 'Email notification sent', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $slack_path . '"', | ||
| 'output' => 'Slack notification sent', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $webhook_path . '"', | ||
| 'output' => 'Webhook notification sent', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify'); | ||
|
|
||
| $this->assertStringContainsString('Started dispatching notifications', $output); | ||
| $this->assertStringContainsString('Email notification sent', $output); | ||
| $this->assertStringContainsString('Slack notification sent', $output); | ||
| $this->assertStringContainsString('Webhook notification sent', $output); | ||
| $this->assertStringContainsString('Finished dispatching notifications', $output); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Optional: assert realpath() success to make failures easier to debug
In the success-path tests for default, single, and multiple channels (Lines 99–165, 167–192), realpath() is used to build the notifier script paths, but its return value isn’t checked. If a script were accidentally missing or misnamed, you’d end up mocking a command with "" rather than getting a clear failure.
Consider adding an explicit assertion before using $script_path:
- $script_path = realpath(__DIR__ . '/../../src/notify-email');
+ $script_path = realpath(__DIR__ . '/../../src/notify-email');
+ $this->assertNotFalse($script_path, 'notify-email script path must resolve via realpath().');and similarly for the Slack/Webhook variants. This will surface configuration issues more clearly during test failures.
Also applies to: 167-192
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyRouterTest.php around lines 99-165: the tests
call realpath() to compute notifier script paths but do not verify the return
value, so a missing/misnamed script yields an empty string in mocks and a
confusing failure; before using the path values assert that realpath(...)
returned a valid path (e.g. assertNotFalse or assertFileExists on the computed
path) and only use the returned value in mockPassthru; apply the same check for
the Slack and Webhook path usages in the other test blocks (lines referenced and
also the 167-192 range mentioned in the comment).
| public function testDefaultEventTypeIsPostDeployment(): void { | ||
| $this->envUnset('VORTEX_NOTIFY_EVENT'); | ||
| $this->envSet('VORTEX_NOTIFY_CHANNELS', 'email'); | ||
|
|
||
| $script_path = realpath(__DIR__ . '/../../src/notify-email'); | ||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $script_path . '"', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify'); | ||
|
|
||
| $this->assertStringContainsString('Finished dispatching notifications', $output); | ||
| } | ||
|
|
||
| public function testProjectFallbackToGenericVariable(): void { | ||
| $this->envUnset('VORTEX_NOTIFY_PROJECT'); | ||
| $this->envSet('VORTEX_PROJECT', 'fallback-project'); | ||
| $this->envSet('VORTEX_NOTIFY_CHANNELS', 'email'); | ||
|
|
||
| $script_path = realpath(__DIR__ . '/../../src/notify-email'); | ||
| $this->mockPassthru([ | ||
| 'cmd' => '"' . $script_path . '"', | ||
| 'result_code' => 0, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify'); | ||
|
|
||
| $this->assertStringContainsString('Finished dispatching notifications', $output); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fallback/login tests don’t currently validate the behavior implied by their names
testProjectFallbackToGenericVariable() (Lines 234–248) and testDefaultLoginUrlGeneration() (Lines 250–264) only assert that notifications “Finished dispatching”; they do not validate that the router actually used the generic project variables or derived login URL, even though the method names suggest this is what they guarantee.
If the router logs the effective project or login URL, or if the notifier scripts expose this via their output, consider tightening these tests to assert on those values (e.g., expecting fallback-project or a derived login URL) so regressions in fallback logic are caught. Otherwise, renaming the tests to reflect what they actually verify would reduce false confidence.
Also applies to: 250-264
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyRouterTest.php lines 219-264 — The two tests
named to validate fallback behavior only assert the generic "Finished
dispatching notifications" message and do not actually verify that the router
used the fallback project or generated the expected login URL; update each test
to either make the mocked notifier emit the effective value and assert it, or
rename the test to match current coverage. Concretely: modify the mockPassthru
response for the notifier script to include a predictable marker containing the
effective project/login (e.g., "PROJECT: fallback-project" or "LOGIN_URL:
https://..."), then after runScript assertStringContainsString for that marker;
if you prefer not to extend mocking, rename the tests to reflect they only
assert dispatch completion.
916116b to
d641987
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (37)
.vortex/tooling/scripts_conversion_plan.md (1)
84-166: Update helper function names in examples to match actual implementationThe planning document still references outdated function names that don't match the actual helpers.php implementation:
load_env()should beload_dotenv()execute_override_if_exists()should beexecute_override()validate_variable()should begetenv_required()validate_command()should becommand_exists()These mismatches appear in multiple sections (lines 84-166, 196-261, 408-417) and could confuse developers implementing scripts based on this plan.
Apply this diff to update the function names throughout the document:
-function load_env() { +function load_dotenv() { // Load .env file-function execute_override_if_exists($script_name) { +function execute_override($script_name) {-function require_env($var_name, $message = null) { +function getenv_required($var_name, $message = null) {-function require_command($command) { +function command_exists($command) {.vortex/tooling/phpcs.xml (1)
37-56: Fix exclude-pattern glob syntaxThe exclude patterns use backslash-escaped dots (
*.Test\.php,*.TestCase\.php) which PHPCS interprets as shell-style globs, not regex. The backslashes are unnecessary and may prevent proper matching.Apply this diff to fix the glob patterns:
- <exclude-pattern>*.Test\.php</exclude-pattern> - <exclude-pattern>*.TestCase\.php</exclude-pattern> - <exclude-pattern>*.test</exclude-pattern> + <exclude-pattern>*Test.php</exclude-pattern> + <exclude-pattern>*TestCase.php</exclude-pattern> + <exclude-pattern>*.test</exclude-pattern>If you need to match test files in subdirectories, use
**/*Test.phppatterns instead..vortex/tooling/tests/Unit/TokenTest.php (1)
32-55: Add test cases for JSON-escaping edge valuesThe data provider covers basic scenarios but misses critical edge cases where
replace_tokens()relies onjson_encode()for escaping. Consider adding test cases for values containing:
- Double quotes:
'He said "hi"'- Backslashes:
'C:\folder\file'- Newlines:
"Line1\nLine2"- Other JSON-escaped characters: tabs, control characters
These tests would lock in the escaping behavior and catch regressions if the implementation changes.
Example additional test cases:
'values with quotes' => [ 'template' => 'Message: %msg%', 'replacements' => ['msg' => 'He said "hello"'], 'expected' => 'Message: He said \"hello\"', ], 'values with backslashes' => [ 'template' => 'Path: %path%', 'replacements' => ['path' => 'C:\folder\file'], 'expected' => 'Path: C:\\folder\\file', ], 'values with newlines' => [ 'template' => 'Text: %text%', 'replacements' => ['text' => "Line1\nLine2"], 'expected' => 'Text: Line1\nLine2', ],.vortex/tooling/src/helpers.php (4)
44-57: Add path traversal protection to execute_overrideThe function concatenates
$pathdirectly into a file path without validation. While current callers passbasename(__FILE__), the function signature accepts any string, creating a potential path traversal vulnerability if used incorrectly in the future.Apply this diff to add validation:
function execute_override(string $path): void { + // Prevent path traversal by ensuring path doesn't contain directory separators. + if (str_contains($path, DIRECTORY_SEPARATOR) || str_contains($path, '/') || str_contains($path, '\\')) { + fail('Invalid script name for override: %s', $path); + } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); if ($dir) { if (!is_dir($dir)) { fail('Custom directory specified in VORTEX_TOOLING_CUSTOM_DIR does not exist: %s', $dir); } $full_path = $dir . DIRECTORY_SEPARATOR . $path;
283-288: Shell-escape$commandto prevent injection riskThe function interpolates
$commanddirectly into a shell string without escaping. While current usage may only pass trusted constants, addingescapeshellarg()is a simple defense-in-depth measure that prevents command injection if usage patterns change.Apply this diff:
function command_exists(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }
409-409: Check curl_init return value to prevent TypeError
curl_init()can returnfalseon failure (e.g., malformed URL). Passingfalsetocurl_setopt_array()on line 431 will cause a TypeError in PHP 8+.Apply this diff:
$ch = curl_init($url); + if ($ch === FALSE) { + return [ + 'ok' => FALSE, + 'status' => 0, + 'body' => FALSE, + 'error' => 'Failed to initialize curl', + 'info' => [], + ]; + } try {
431-431: Check curl_setopt_array return valueWhile less critical than the
curl_initcheck,curl_setopt_array()returnsfalseif setting options fails. Checking the return value would allow graceful handling of configuration errors rather than proceeding with a misconfigured handle.Apply this diff:
curl_setopt_array($ch, $opts); + if (curl_errno($ch)) { + $error = curl_error($ch); + return [ + 'ok' => FALSE, + 'status' => 0, + 'body' => FALSE, + 'error' => $error, + 'info' => [], + ]; + } // With CURLOPT_RETURNTRANSFER, curl_exec returns string|false..vortex/tooling/src/notify-webhook (2)
16-17: Fix PHPCS: remove blank line after separator commentsThere’s still an empty line after the
// -----------------------------------------------------------------------------separators (Lines 16 and 63), which violatesDrupal.Commenting.InlineComment.SpacingAfteras flagged by the tooling pipeline. Remove the blank lines so the next code/comment line follows immediately.Also applies to: 63-64
44-49: Filter out empty headers fromVORTEX_NOTIFY_WEBHOOK_HEADERSIf the headers env var has trailing or double pipes,
explode('|', $webhook_headers)can produce empty strings that are passed through torequest(). Trim and then drop empty entries for cleaner behavior.-$response = request($webhook_url, [ - 'method' => $webhook_method, - 'headers' => array_map(trim(...), explode('|', $webhook_headers)), - 'body' => $webhook_payload, -]); +$response = request($webhook_url, [ + 'method' => $webhook_method, + 'headers' => array_filter( + array_map(trim(...), explode('|', $webhook_headers)), + static fn(string $header): bool => $header !== '' + ), + 'body' => $webhook_payload, +]);Also applies to: 97-102
.vortex/tooling/src/notify-slack (2)
18-19: Fix PHPCS: remove blank line after separator commentsThe separator comments at Lines 18 and 62 are still followed by a blank line, which violates
Drupal.Commenting.InlineComment.SpacingAfterand is flagged by the tooling checks. Drop the empty lines so code follows immediately.Also applies to: 62-63
128-134: Optionally validate Slack response body, not just HTTP 200Slack webhooks can return HTTP 200 with an error string body (e.g.,
invalid_payload), which would currently be treated as success. If you want stricter detection of misconfiguration, also inspect the body (e.g., expect'ok').-$response = request_post($slack_webhook, $payload, [ - 'Content-Type: application/json', -]); - -if ($response['status'] !== 200) { - fail('Unable to send notification to Slack. HTTP status: %s', $response['status']); -} +$response = request_post($slack_webhook, $payload, [ + 'Content-Type: application/json', +]); + +$body = (string) ($response['body'] ?? ''); +if ($response['status'] !== 200 || trim($body) !== 'ok') { + fail( + 'Unable to send notification to Slack. HTTP status: %s, body: %s', + $response['status'], + $body + ); +}.vortex/tooling/tests/Unit/GetenvTest.php (2)
88-93: Fixgetenv_defaultexception message expectation
helpers.phpthrows:throw new \InvalidArgumentException( 'getenv_default() requires at least 2 arguments: one or more variable names and a default value' );but this test expects only
'getenv_default() requires at least 2 arguments'. The mismatch will cause the test to fail.Update the expectation to match the full message or relax it to
assertStringContainsString('getenv_default() requires at least 2 arguments', ...).
137-171:captureOutput+expectExceptionmakes output assertions unreachableIn the
getenv_requiredfailure tests, you:
- Call
$this->mockQuit(1);- Set
expectException(QuitErrorException::class);- Assign
$output = $this->captureOutput(...)- Assert against
$output.However,
captureOutput()rethrows on anyThrowableand never returns when an exception is thrown, so the assignment to$outputnever completes and the subsequentassertStringContainsString(..., $output)lines are never executed. The tests currently only verify that an exception is thrown, not that the expected error text is emitted.Refactor these tests to either:
- Drop
expectException()and wrap thegetenv_required(...)call in atry/catch (QuitErrorException $e)that also captures buffered output (e.g., usingob_start()/ob_get_clean()directly in the test), or- Adjust
captureOutput()to not be used together withexpectException, and instead have a dedicated helper that returns both$outputand the caught exception for assertion.As written, the output assertions are dead code.
Also applies to: 180-189
.vortex/tooling/tests/Unit/NotifyGithubTest.php (1)
23-31: Avoid PAT‑like test tokens to satisfy secret scannersThe hard-coded values
'ghp_test123456'and'ghp_fallback123'resemble real GitHub personal access tokens and are flagged by Gitleaks as a generic API key, even though they’re only test data.To keep tests representative without tripping secret detection, switch to clearly fake/non-PAT formats (e.g.
'test-token','GITHUB_TOKEN_TEST', or a'ghp_'prefix assembled from concatenated strings) in env setup and header expectations.Also applies to: 47-48, 72-73, 91-92, 117-118, 133-134, 147-148, 160-161, 198-199, 210-211, 235-236
.vortex/tooling/src/notify-email (1)
100-114: Sanitize email header values to prevent header injectionValues derived from environment/config (
$email_from,$email_subject,$email_recipients→$name) are interpolated directly intoTo:,Subject:, andFrom:headers and into thesendmail/\r/\nsequences.Introduce a small sanitizer for header fields and use it consistently before composing headers and shell commands. For example:
@@ -$email_message = getenv_default('VORTEX_NOTIFY_EMAIL_MESSAGE', "## This is an automated message ##\nSite %project% %label% has been deployed at %timestamp% and is available at %environment_url%.\nLogin at: %login_url%"); +$email_message = getenv_default('VORTEX_NOTIFY_EMAIL_MESSAGE', "## This is an automated message ##\nSite %project% %label% has been deployed at %timestamp% and is available at %environment_url%.\nLogin at: %login_url%"); + +/** + * Sanitize an email header value to prevent header injection. + */ +function sanitize_email_header(string $value): string { + return str_replace(["\r", "\n", "\0"], '', $value); +} @@ -$email_recipients = array_map(trim(...), explode(',', $email_recipients)); +$email_recipients = array_filter( + array_map(trim(...), explode(',', $email_recipients)), + static fn(string $value): bool => $value !== '' +); @@ - $parts = array_map(trim(...), explode('|', $email_recipient, 2)); - $email = $parts[0]; - $name = $parts[1] ?? ''; + $parts = array_map(trim(...), explode('|', $email_recipient, 2)); + $email = sanitize_email_header($parts[0]); + $name = sanitize_email_header($parts[1] ?? ''); + + $safe_subject = sanitize_email_header($email_subject); + $safe_from = sanitize_email_header($email_from); @@ - $to = empty($name) ? $email : sprintf('"%s" <%s>', $name, $email); + $to = empty($name) ? $email : sprintf('"%s" <%s>', addslashes($name), $email); @@ - $mail_content .= sprintf('Subject: %s%s', $email_subject, PHP_EOL); - $mail_content .= sprintf('From: %s%s', $email_from, PHP_EOL); + $mail_content .= sprintf('Subject: %s%s', $safe_subject, PHP_EOL); + $mail_content .= sprintf('From: %s%s', $safe_from, PHP_EOL); @@ - $cmd = sprintf('echo %s | sendmail -t -f %s', escapeshellarg($mail_content), escapeshellarg($email_from)); + $cmd = sprintf('echo %s | sendmail -t -f %s', escapeshellarg($mail_content), escapeshellarg($safe_from)); @@ - $mail_content = "From: {$email_from}\n\n{$email_message}"; - $cmd = sprintf('echo %s | mail -s %s %s', escapeshellarg($mail_content), escapeshellarg($email_subject), escapeshellarg($to)); + $mail_content = "From: {$safe_from}\n\n{$email_message}"; + $cmd = sprintf('echo %s | mail -s %s %s', escapeshellarg($mail_content), escapeshellarg($safe_subject), escapeshellarg($to));This keeps behavior while closing the header injection vector and also filters out empty recipient entries.
Also applies to: 121-155
.vortex/tooling/src/notify-newrelic (1)
95-116: Fix app‑ID discovery control flow; avoid posting with empty application IDIn the discovery block:
if ($response['ok'] && $response['body']) { $data = json_decode((string) $response['body'], TRUE); if (isset($data['applications'][0]['id'])) { $newrelic_appid = $data['applications'][0]['id']; } if (!$newrelic_appid) { pass('Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', $newrelic_app_name); quit(); } } pass('Discovered application ID: %s', $newrelic_appid);If the HTTP request fails or the body is empty, the
ifblock is skipped,$newrelic_appidremains empty, but you still log "Discovered application ID" and later attempt to POST to/applications//deployments.json, only failing at the final status check. That’s misleading and fragile.Consider:
$response = request_get($url, [ 'Api-Key: ' . $newrelic_user_key, ]); - if ($response['ok'] && $response['body']) { - $data = json_decode((string) $response['body'], TRUE); - if (isset($data['applications'][0]['id'])) { - $newrelic_appid = $data['applications'][0]['id']; - } - - if (!$newrelic_appid) { - pass('Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', $newrelic_app_name); - quit(); - } - } - - pass('Discovered application ID: %s', $newrelic_appid); + if ($response['ok'] && $response['body']) { + $data = json_decode((string) $response['body'], TRUE); + if (is_array($data) && isset($data['applications'][0]['id'])) { + $newrelic_appid = $data['applications'][0]['id']; + } + + if (!$newrelic_appid) { + pass('Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', $newrelic_app_name); + quit(); + } + + pass('Discovered application ID: %s', $newrelic_appid); + } + else { + pass('Notification skipped: Unable to discover New Relic application ID for %s. API request failed.', $newrelic_app_name); + quit(); + }This ensures you never proceed to the deployment POST with an empty app ID and handles both "no app found" and "API failure" as explicit skip paths.
Also applies to: 149-159
.vortex/tooling/tests/Unit/RequestTest.php (1)
13-15: Avoid real network‑coupled tests in the unit suiteAll tests here rely on live HTTP calls to
example.com(including a timeout case to port 81). That couples the unit suite to external DNS/network availability and example.com’s behavior, making it brittle and potentially flaky in CI or locked‑down environments.Prefer to:
- Test
request_get,request_post, andrequestusingMockTrait/mockRequest*to validate options and normalized response shape; and/or- Reclassify these as explicit integration tests (separate group) so they can be skipped or isolated when network access is unavailable.
Also applies to: 30-100
.vortex/tooling/src/notify-github (2)
56-66: Relax upper-bound length check for deployment IDs
deployment_id_is_valid()enforces 9–11 digits. GitHub deployment IDs may grow; keeping the lower bound and numeric check but dropping the<= 11constraint would avoid future false negatives while still rejecting bad values.Example:
- // Check length (9-11 digits) and numeric format. - return $length >= 9 && $length <= 11 && ctype_digit($id_str); + // Check minimum length and numeric format. + return $length >= 9 && ctype_digit($id_str);
84-88: Set explicit JSON Content-Type for GitHub POST requestsBoth deployment creation and status update send JSON bodies but only set
Accept. AddingContent-Type: application/jsonclarifies intent and avoids relying on cURL defaults when talking to the GitHub API.$response = request_post($url, json_encode($payload_data), [ 'Authorization: token ' . $github_token, - 'Accept: application/vnd.github.v3+json', + 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]); @@ $response = request_post($url, json_encode($status_data), [ - 'Accept: application/vnd.github.v3+json', - 'Authorization: token ' . $token, + 'Accept: application/vnd.github.v3+json', + 'Authorization: token ' . $token, + 'Content-Type: application/json', ]);Also applies to: 146-150
.vortex/tooling/src/notify-jira (3)
75-99: Fix Jira ADF documentcontentshape
text_to_adf()returns a single paragraph node, butcomment_body['content']is set to that node directly. Jira’s ADFdocschema expectscontentto be an array of block nodes.Wrap the paragraph node:
$jira_message_adf = text_to_adf($jira_message); $comment_body = [ 'type' => 'doc', 'version' => 1, - 'content' => $jira_message_adf, + 'content' => [ + $jira_message_adf, + ], ];Also applies to: 153-159
161-170: Avoid logging raw Jira user email by default
note('User email : ' . $jira_user_email);emits a concrete email address to standard output, which may be considered PII in CI logs.Consider either removing this line, masking the email (e.g., local‑part initial + domain), or guarding it behind a debug/verbose flag.
191-228: Check Jira responses for transition and assignee updatesThe transition POST (
request_poston the transitions endpoint) and the assigneePUT(request) ignore HTTP responses. If Jira returns 4xx/5xx or malformed JSON, the script still logs success, making failures hard to spot.Consider:
- Capturing the responses.
- Failing if
$response['ok']is false or the body is missing/invalid, similar to earlier authentication and comment creation checks.Also applies to: 230-269
.vortex/tooling/src/notify (1)
65-67: Use DIRECTORY_SEPARATOR when building script pathThe router builds per‑channel script paths with a hardcoded
'/'. For portability, especially if these tools might ever run on non‑Unix PHP, preferDIRECTORY_SEPARATOR:- $script = __DIR__ . ('/notify-' . $channel); + $script = __DIR__ . DIRECTORY_SEPARATOR . 'notify-' . $channel;.vortex/tooling/tests/Unit/FormatterTest.php (3)
35-36: Use EnvTrait envSet/envUnset instead of raw putenv for TERM in testsThese tests mutate
TERMdirectly viaputenv(), bypassing EnvTrait’s tracking andenvReset()inUnitTestCase::tearDown(). That can leak TERM state between tests and make them order‑dependent.Switch to EnvTrait helpers so env changes are tracked and reset, e.g.:
public function testOutputFormatters(string $function, string $expected_prefix): void { - putenv('TERM=dumb'); + $this->envSet('TERM', 'dumb'); @@ public function testOutputFormattersWithColor(string $function, string $expected_prefix): void { - putenv('TERM=xterm-256color'); + $this->envSet('TERM', 'xterm-256color'); @@ public function testTermSupportsColor(string|bool $term_value, bool $expected): void { if ($term_value === FALSE) { - putenv('TERM'); + $this->envUnset('TERM'); } else { - putenv('TERM=' . $term_value); + $this->envSet('TERM', $term_value); }This keeps env mutations consistent with the rest of the tooling tests.
Also applies to: 64-65, 102-107
77-83: Add colored coverage forfail_no_exitindataProviderOutputFormattersWithColor()The with‑color data provider exercises
task,info, andpass, but notfail_no_exit, even thoughfail_no_exithas a distinct colored prefix. That leaves a gap in formatter coverage under color‑enabled terminals.Consider adding a case, e.g.:
public static function dataProviderOutputFormattersWithColor(): array { return [ 'task' => ['task', '[TASK]'], 'info' => ['info', '[INFO]'], 'pass' => ['pass', '[ OK ]'], + 'fail_no_exit' => ['fail_no_exit', '[FAIL]'], ]; }(Adjust
$expected_prefixto match the actual colored output if you want to assert on the full ANSI sequence.)
100-118: Add a positive TERM case todataProviderTermSupportsColor()The provider only covers cases where color support is expected to be
FALSE. Adding at least one “color‑capable” TERM (e.g.xterm-256color) improves confidence thatterm_supports_color()can returnTRUEwhen both TERM andposix_isatty(STDOUT)allow it.For example:
public static function dataProviderTermSupportsColor(): array { return [ 'dumb terminal' => ['term_value' => 'dumb', 'expected' => FALSE], 'no terminal' => ['term_value' => FALSE, 'expected' => FALSE], + 'color terminal' => ['term_value' => 'xterm-256color', 'expected' => TRUE], ]; }You may need to mock
posix_isatty()or ensure CI runs with a TTY‑like STDOUT to keep this deterministic..vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
21-32: Loadhelpers.phpinsetUp()so all tests are independent
helpers.phpis required only insidetestMockRequestGetSuccess(). All later tests call\DrevOps\VortexTooling\request_get/post/requestbut never ensure the helpers are loaded themselves. This couples the class to execution order and will break when running a single later test in isolation (undefined function).Move the
require_onceintosetUp()and drop the inline require, e.g.:#[CoversClass(UnitTestCase::class)] class MockRequestSelfTest extends UnitTestCase { + protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockRequestGetSuccess(): void { @@ - // Load helpers.php AFTER setting up mocks. - require_once __DIR__ . '/../../src/helpers.php'; - - $result = \DrevOps\VortexTooling\request_get('https://example.com/api'); + $result = \DrevOps\VortexTooling\request_get('https://example.com/api');The curl mocks created by
mockRequest*will still work correctly with this arrangement..vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
61-107: Script-based quit tests never reach the output assertionsIn the script tests (
testMockQuitScript0*/testMockQuitScript1*), the flow is:
mockQuit(...)sets up a mock that throwsQuitSuccessException/QuitErrorExceptionorRuntimeException.- The test calls
expectException(...)/expectExceptionCode(...).$this->runScript('tests/Fixtures/...')is invoked.runScript()catches the QuitException, captures the buffer, and rethrows a new QuitException with the output attached.- PHPUnit sees the expected exception and immediately ends the test method.
Lines 69–71, 81–83, 93–95, and 105–107 are therefore unreachable; the tests never actually assert on
$output, so they don’t validate the script output they appear to check.Refactor to capture both exception and output explicitly, e.g.:
public function testMockQuitScript0Success(): void { $this->mockQuit(); - - $this->expectException(QuitSuccessException::class); - $this->expectExceptionCode(0); - - $output = $this->runScript('tests/Fixtures/test-quit-passing'); - - $this->assertStringContainsString('Script will exit with code 0', $output); - $this->assertStringNotContainsString('ERROR Script continued after quit()', $output); + try { + $this->runScript('tests/Fixtures/test-quit-passing'); + $this->fail('Expected QuitSuccessException was not thrown.'); + } + catch (QuitSuccessException $e) { + $this->assertSame(0, $e->getCode()); + $output = $e->getOutput(); + $this->assertStringContainsString('Script will exit with code 0', $output); + $this->assertStringNotContainsString('ERROR Script continued after quit()', $output); + } }Apply the same pattern to the other three script tests, catching
QuitErrorExceptionorRuntimeExceptionas appropriate and asserting on$e->getOutput().This way you genuinely exercise both the exception behavior and the emitted output.
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (4)
9-13: Update docblock to referencecheck-no-exit.phpThe class docblock still mentions
check-exit-usage.phpand a generic “exit() usage checker”, but$this->scriptPathpoints tocheck-no-exit.php. Aligning the comment with the actual script name/behavior will avoid confusion when tracking which tool these self-tests cover.
29-36: Avoid reusing a fixed$testDiracross tests (mkdir warnings and order‑dependent behavior)
setUp()always uses the same$this->testDir = self::$tmp . '/check-exit-test';and runsmkdir()on it and its subdirectories before every test. WithfailOnWarning="true"this has two problems:
- Re-running
mkdir()on existing directories will emit warnings and fail the suite.- Files created by earlier tests persist into later ones, so
testHandlesEmptyDirectories()is not actually running against an empty tree and becomes order‑dependent.Use a unique temp directory per test and recursive mkdir, e.g.:
protected function setUp(): void { parent::setUp(); $this->scriptPath = dirname(__DIR__, 2) . '/check-no-exit.php'; - $this->testDir = self::$tmp . '/check-exit-test'; - mkdir($this->testDir); - mkdir($this->testDir . '/src'); - mkdir($this->testDir . '/tests'); + $this->testDir = self::$tmp . '/check-exit-test-' . uniqid('', true); + mkdir($this->testDir . '/src', 0777, true); + mkdir($this->testDir . '/tests', 0777, true); }This keeps the tests isolated and avoids mkdir‑on‑existing‑dir warnings.
Also applies to: 131-138
47-50: Factor outexec()into a helper and escape shell argumentsEach test builds the same shell command with
sprintf('cd %s && php %s 2>&1', $this->testDir, $this->scriptPath). This:
- Duplicates logic in six places.
- Does not escape
$this->testDiror$this->scriptPath, so paths with spaces/special characters would break.Refactor into a small helper that uses
escapeshellarg()and reuse it, e.g.:/** * Run check-no-exit.php from the current test directory. * * @return array{0: string[], 1: int} */ private function runCheckNoExitScript(): array { $output = []; $exit_code = 0; $cmd = sprintf( 'cd %s && php %s 2>&1', escapeshellarg($this->testDir), escapeshellarg($this->scriptPath), ); exec($cmd, $output, $exit_code); return [$output, $exit_code]; }Then replace each inline
exec(...)block with:[$output, $exit_code] = $this->runCheckNoExitScript();This removes duplication and makes the command robust to unusual paths.
Also applies to: 65-68, 82-84, 103-105, 120-122, 133-135
113-126: Also assert on the non‑zero exit code intestReportsCorrectLineNumbers()This test only checks that
'line-test.php:5'appears in the output. If the script ever reported the right line but erroneously exited with code 0, the test would still pass.Tighten it by asserting the exit code as well, similar to your other “exit detected” tests, e.g.:
$output = []; $exit_code = 0; @@ exec(sprintf('cd %s && php %s 2>&1', $this->testDir, $this->scriptPath), $output, $exit_code); $output_str = implode("\n", $output); + $this->assertEquals(1, $exit_code); $this->assertStringContainsString('line-test.php:5', $output_str);This keeps behavior consistent across the suite and makes the test more meaningful.
.vortex/tooling/tests/Unit/NotifyRouterTest.php (1)
194-264: Strengthen “default event” and fallback/login tests or rename them
testSuccessfulNotificationWithValidEventTypes(),testDefaultEventTypeIsPostDeployment(),testProjectFallbackToGenericVariable(), andtestDefaultLoginUrlGeneration()currently only assert generic success/“Finished dispatching notifications”, even though their names imply they validate the resolved event (post_deployment), project fallback (fallback-project), and derived login URL.Without asserting on any value that uniquely identifies those behaviors (e.g., an explicit log line or notifier output that includes the event, project, or login URL), regressions in defaulting/fallback logic could slip through while these tests still pass.
Consider either:
- Extending the router/notifier output or passthru mocks so these tests can assert concrete markers (e.g., resolved event, effective project, generated login URL), or
- Renaming the tests to reflect the narrower behavior they currently guarantee (successful dispatch under those env conditions).
.vortex/tooling/tests/Unit/NotifySlackTest.php (1)
33-151: Slack payload callbacks are never exercised becausemockRequestPost()ignores the$bodyargumentAll the success-path tests that try to validate the Slack JSON payload (
testSuccessfulNotificationPostDeployment(),testSuccessfulNotificationPreDeployment(),testSuccessfulNotificationWithCustomMessage(),testSuccessfulNotificationWithChannel(),testSuccessfulNotificationWithCustomBot(),testTokenReplacementInMessage()) pass a$this->callback(...)as the$bodyargument tomockRequestPost().However, the current
MockTrait::mockRequestPost()implementation only forwardsurl,methodandresponsetomockRequestMultiple, discarding$body,$headers, and$timeout. As a result, those callbacks are never invoked and the payload assertions inside them never actually run; the tests only verify that some POST request was mocked and that the script’s console output looks right.To turn these into real payload tests you’ll need to extend the mocking layer, for example:
- Update
mockRequestPost()to pass throughbody,headers, andtimeoutinto the request definition given tomockRequestMultiple, e.g.:// In MockTrait.php protected function mockRequestPost( string $url, $body = NULL, array $headers = [], int $timeout = 10, ?array $response = NULL, string $namespace = 'DrevOps\\VortexTooling', ): void { $this->mockRequestMultiple([[ 'url' => $url, 'method' => 'POST', 'body' => $body, 'headers' => $headers, 'timeout' => $timeout, 'response' => $response ?? [], ]], $namespace); }
- Ensure the underlying HTTP stub (used by
mockRequestMultiple) detects when'body'is a PHPUnit callback and calls it with the actual request payload string so your inline assertions execute.Once that’s wired, these tests will start truly validating payload shape and token replacement rather than just success/failure logs.
Also applies to: 185-252
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (1)
36-82: New Relic payload/header callbacks are ineffective untilmockRequestPost()propagates themSeveral tests rely on inspecting the outbound JSON/body or headers via
$this->callback(...)passed intomockRequestPost():
testSuccessfulNotificationWithProvidedAppId()testSuccessfulNotificationWithAutoGeneratedRevision()testSuccessfulNotificationWithCustomDescription()testSuccessfulNotificationWithCustomChangelog()testNotificationFailureWhenDeploymentCreationFails()testFallbackToGenericVariables()But, as in the Slack tests,
MockTrait::mockRequestPost()currently ignores the$body,$headers, and$timeoutparameters and only forwardsurl,method, andresponsetomockRequestMultiple. That means:
- The callbacks are never invoked with the real payload, so assertions on
deployment.revision,deployment.user,description,changelog, etc. never actually run.- Header expectations (e.g.,
Api-Key: …) are not validated at the HTTP-mock level.Once
mockRequestPost()and the underlying HTTP stub are updated to carry and consumebody/headers(e.g., invoking callbacks with the actual request body), these tests will start exercising the New Relic integration end-to-end rather than just checking console output.Also applies to: 132-152, 154-174, 176-206, 208-252
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
48-56: Request body callback assertions are not executedThe test passes a callback to
mockRequestPost()to validate the request payload structure (lines 50-56). However, based on theMockTrait::mockRequestPost()implementation shown in the relevant code snippets, the$bodyparameter is accepted but never used—it's not forwarded tomockRequestMultiple().This means:
- The callback on lines 50-56 (and similar callbacks on lines 118-123, etc.) is never invoked
- Assertions inside these callbacks never execute
- Tests provide false confidence about payload validation
To fix, either:
- Update
MockTrait::mockRequestPost()to capture and validate body via the callback, or- Change tests to validate request bodies through a different mechanism (e.g., by inspecting recorded calls after the fact)
Also applies to: Lines 118-123, and other callback uses throughout the file
333-362: Test doesn't verify custom message renderingWhile the test sets
VORTEX_NOTIFY_JIRA_MESSAGEwith a template containing tokens (line 334), it only asserts that the notification finished successfully (line 361). It doesn't verify that:
- The custom message was actually rendered
- Token substitution occurred (%project%, %label%, %timestamp%)
- The rendered message appears in the output or request payload
Consider adding assertions to verify the custom message template is properly rendered and used.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (36)
.vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/plan.test-notify.md(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Fixtures/test-override-failing(1 hunks).vortex/tooling/tests/Fixtures/test-override-passing(1 hunks).vortex/tooling/tests/Self/CheckNoExitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Unit/CommandExistsTest.php(1 hunks).vortex/tooling/tests/Unit/DotenvTest.php(1 hunks).vortex/tooling/tests/Unit/FormatterTest.php(1 hunks).vortex/tooling/tests/Unit/GetenvTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyEmailTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyGithubTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyJiraTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyNewrelicTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyRouterTest.php(1 hunks).vortex/tooling/tests/Unit/NotifySlackTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyWebhookTest.php(1 hunks).vortex/tooling/tests/Unit/OverrideTest.php(1 hunks).vortex/tooling/tests/Unit/RequestTest.php(1 hunks).vortex/tooling/tests/Unit/TokenTest.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Fixtures/test-override-passing.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Unit/TokenTest.php.vortex/tooling/tests/Unit/DotenvTest.php.vortex/tooling/tests/Unit/CommandExistsTest.php.vortex/tooling/tests/Unit/NotifyRouterTest.php.vortex/tooling/tests/Unit/GetenvTest.php.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Unit/NotifyJiraTest.php.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Self/CheckNoExitSelfTest.php.vortex/tooling/tests/Unit/NotifySlackTest.php.vortex/tooling/tests/Unit/FormatterTest.php.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Unit/NotifyEmailTest.php.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Unit/UnitTestCase.php
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.
Applied to files:
.vortex/tooling/tests/Fixtures/test-override-passing.vortex/tooling/tests/Fixtures/test-override-failing
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/CheckNoExitSelfTest.php
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/NotifyEmailTest.php
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Fixtures/test-override-failing
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.vortex/tooling/tests/Unit/NotifyGithubTest.php
🧬 Code graph analysis (22)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
__construct(22-31)getOutput(39-41)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (3)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (5)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)mockRequestAssertAllMocksConsumed(458-469)mockRequestMultiple(229-359).vortex/tooling/src/helpers.php (3)
request_get(346-352)request_post(374-381)request(403-460)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
__construct(22-31)getOutput(39-41)
.vortex/tooling/tests/Unit/TokenTest.php (1)
.vortex/tooling/src/helpers.php (1)
replace_tokens(301-317)
.vortex/tooling/tests/Unit/RequestTest.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(346-352)request_post(374-381)request(403-460)
.vortex/tooling/tests/Unit/NotifyGithubTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/tests/Unit/DotenvTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/src/helpers.php (1)
load_dotenv(65-99)
.vortex/tooling/tests/Unit/CommandExistsTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
command_exists(283-288)fail(265-268).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Unit/NotifyRouterTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScriptEarlyPass(94-106)runScriptError(108-120)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (1)
mockPassthru(161-163)
.vortex/tooling/tests/Unit/GetenvTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
getenv_default(124-141)getenv_required(166-187).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212)
.vortex/tooling/tests/Unit/OverrideTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/src/helpers.php (1)
execute_override(44-57)
.vortex/tooling/tests/Unit/NotifyWebhookTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequest(373-389)
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (3)
mockRequestGet(405-418)mockRequestPost(436-450)mockRequest(373-389)
.vortex/tooling/tests/Self/MockQuitSelfTest.php (5)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (1)
quit(481-483)
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28)
.vortex/tooling/tests/Unit/NotifySlackTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequestPost(436-450)
.vortex/tooling/tests/Unit/FormatterTest.php (4)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(196-212).vortex/tooling/src/helpers.php (2)
fail(265-268)term_supports_color(273-275)
.vortex/tooling/tests/Unit/NotifyEmailTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120)
.vortex/tooling/tests/Self/MockPassthruSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (2)
mockPassthru(161-163)mockPassthruAssertAllMocksConsumed(171-182)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(436-450)mockRequestGet(405-418)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
.vortex/tooling/tests/Unit/UnitTestCase.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (2)
QuitErrorException(12-43)getOutput(39-41).vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
QuitSuccessException(12-43)getOutput(39-41).vortex/tooling/tests/Traits/MockTrait.php (2)
mockTearDown(63-81)mockQuit(196-212)
🪛 Gitleaks (8.29.0)
.vortex/tooling/tests/Unit/NotifyGithubTest.php
[high] 24-24: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php
[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 48-48: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 73-73: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 91-91: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 100-100: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 118-118: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 144-144: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 166-166: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 183-183: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
[high] 200-200: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~230-~230: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
.vortex/tooling/plan.test-notify.md
[style] ~400-~400: To elevate your writing, try using a synonym here.
Context: ...t count per script - Any lines that are hard to cover (with explanation) - Overall c...
(HARD_TO)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/scripts_conversion_plan.md
9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
17-17: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
17-17: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
43-43: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
48-48: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
59-59: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
68-68: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
92-92: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
93-93: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
101-101: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
103-103: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
112-112: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
125-125: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
126-126: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
139-139: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
140-140: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
146-146: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
147-147: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
167-167: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
182-182: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
278-278: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
283-283: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
386-386: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
393-393: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
394-394: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
395-395: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
396-396: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
397-397: Ordered list item prefix
Expected: 4; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
398-398: Ordered list item prefix
Expected: 5; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
399-399: Ordered list item prefix
Expected: 6; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
401-401: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
402-402: Ordered list item prefix
Expected: 1; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
403-403: Ordered list item prefix
Expected: 2; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
404-404: Ordered list item prefix
Expected: 3; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
405-405: Ordered list item prefix
Expected: 4; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
406-406: Ordered list item prefix
Expected: 5; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
431-431: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
432-432: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
440-440: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
441-441: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
453-453: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
454-454: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
466-466: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
467-467: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
475-475: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
476-476: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
489-489: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
496-496: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
503-503: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
.vortex/tooling/plan.test-notify.md
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
83-83: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
129-129: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
160-160: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
218-218: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
233-233: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
243-243: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
261-261: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
306-306: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
400-400: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
407-407: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
412-412: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: build (1)
- GitHub Check: build (0)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-installer (8.3)
| quit(0); | ||
| } | ||
|
|
||
| $channels = array_filter(array_map(trim(...), explode(',', $channels))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Confirm PHP version for first-class callable usage in array_map
array_map(trim(...), ...) relies on first‑class callable syntax, which requires PHP 8.1+. If this tooling is expected to run on older PHP versions, this will fatally error and should be changed to array_map('trim', ...).
Please verify the minimum PHP version declared for the tooling package and align this call accordingly.
🏁 Script executed:
#!/bin/bash
# Find and display composer.json in .vortex/tooling
find .vortex/tooling -name "composer.json" -type f | head -5Length of output: 29
🏁 Script executed:
#!/bin/bash
# Display the notify file around line 54
if [ -f ".vortex/tooling/src/notify" ]; then
cat ".vortex/tooling/src/notify" | head -60 | tail -15
fiLength of output: 302
🏁 Script executed:
#!/bin/bash
# Search for the notify file if the exact path doesn't exist
find . -name "notify*" -path "*/tooling/*" -type f 2>/dev/null | head -10Length of output: 243
🏁 Script executed:
#!/bin/bash
# Display the composer.json to check PHP version requirement
cat .vortex/tooling/composer.jsonLength of output: 1988
Add PHP 8.1+ requirement to composer.json
The code at line 54 uses first-class callable syntax (trim(...)), which requires PHP 8.1+. However, the tooling's composer.json has no "require" section declaring a minimum PHP version. Without this constraint, the package can be installed on incompatible PHP versions and will fatally error at runtime.
Add "php": ">=8.1" to the "require" section in .vortex/tooling/composer.json.
🤖 Prompt for AI Agents
In .vortex/tooling/src/notify around line 54 the code uses the PHP 8.1
first-class callable syntax (trim(...)), but .vortex/tooling/composer.json does
not declare a minimum PHP version; update .vortex/tooling/composer.json to add a
"require" section (or merge into the existing one) including "php": ">=8.1" so
Composer enforces the correct PHP version and prevents installation on
incompatible runtimes.
| $this->envSetMultiple([ | ||
| 'VORTEX_NOTIFY_NEWRELIC_PROJECT' => 'test-project', | ||
| 'VORTEX_NOTIFY_NEWRELIC_USER_KEY' => 'NRAK-TEST123456', | ||
| 'VORTEX_NOTIFY_NEWRELIC_LABEL' => 'main', | ||
| 'VORTEX_NOTIFY_NEWRELIC_ENVIRONMENT_URL' => 'https://example.com', | ||
| 'VORTEX_NOTIFY_NEWRELIC_LOGIN_URL' => 'https://example.com/login', | ||
| 'VORTEX_NOTIFY_NEWRELIC_APP_NAME' => 'test-app', | ||
| 'VORTEX_NOTIFY_NEWRELIC_USER' => 'Deploy Bot', | ||
| 'VORTEX_NOTIFY_NEWRELIC_EVENT' => 'post_deployment', | ||
| 'VORTEX_NOTIFY_NEWRELIC_APPID' => '12345678', | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Static-analysis is flagging the fake New Relic keys; consider obfuscating them
Gitleaks is reporting multiple “Generic API Key” hits on the hardcoded values like NRAK-TEST123456 and NRAK-FALLBACK456 in this test. While these are clearly non-production placeholders, leaving them in this form can still cause CI noise or even hard failures in stricter pipelines.
Options to reduce that friction:
- Adjust the dummy values so they no longer match common key patterns (e.g.,
NRAK-00000000000000000000orNRAK-XXXX-PLACEHOLDER), or - Add a targeted allowlist/ignore rule for these specific test files/strings in your gitleaks configuration.
This keeps security tooling happy without losing the readability of the tests.
Also applies to: 231-243
🧰 Tools
🪛 Gitleaks (8.29.0)
[high] 25-25: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php lines 23-33 (and similarly
231-243): the test hardcodes New Relic-like API keys that trigger Gitleaks
“Generic API Key” matches; replace those dummy values with non-matching
placeholder patterns (e.g., use clearly non-secret strings like
"NRAK-XXXX-PLACEHOLDER" or "NRAK-00000000000000000000") or alternatively add a
targeted gitleaks allowlist/ignore rule for this test file; update all
occurrences in the file to the chosen approach so static-analysis no longer
flags them.
| $this->callback(function ($body): true { | ||
| /** @var array {username: string, icon_emoji: string, attachments: array} $payload */ | ||
| $payload = json_decode($body, TRUE); | ||
| // Verify payload structure. | ||
| $this->assertEquals('Deployment Bot', $payload['username']); | ||
| $this->assertEquals(':rocket:', $payload['icon_emoji']); | ||
| $this->assertEquals('good', $payload['attachments'][0]['color']); | ||
| $this->assertStringContainsString('Deployment Complete', $payload['attachments'][0]['title']); | ||
| $this->assertEquals('test-project', $this->getFieldValue($payload, 'Deployment')); | ||
| return TRUE; | ||
| }), | ||
| ['Content-Type: application/json'], | ||
| 10, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Fix array-shape phpdoc syntax so static analysis tools can understand payload structure
The phpdoc comments like:
/** @var array {username: string, icon_emoji: string, attachments: array} $payload */use array {…} instead of the standard shape syntax array{…} expected by tools such as PHPStan/Psalm. This likely means the annotations are ignored or misparsed.
Consider updating them to valid array-shape syntax, for example:
/** @var array{username: string, icon_emoji: string, attachments: array} $payload */(and similarly for the other Slack payload docs). This keeps IDE/static-analysis feedback aligned with what the tests are asserting.
Also applies to: 65-72, 88-93, 110-114, 135-140
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifySlackTest.php around lines 36-48 (also apply
same change at 65-72, 88-93, 110-114, 135-140): the phpdoc array-shape
annotations use invalid syntax `array { ... }` which static analyzers ignore;
replace each occurrence with the proper shape syntax `array{...}` (e.g. change
`/** @var array {username: string, icon_emoji: string, attachments: array}
$payload */` to `/** @var array{username: string, icon_emoji: string,
attachments: array} $payload */`) so tools like PHPStan/Psalm and IDEs correctly
recognize the payload structure.
| public function testSuccessfulNotificationWithCustomPayload(): void { | ||
| $this->envSet('VORTEX_NOTIFY_WEBHOOK_PAYLOAD', '{"custom": "%project%", "info": "%label%"}'); | ||
|
|
||
| $this->mockRequest( | ||
| 'https://webhook.example.com/endpoint', | ||
| ['method' => 'POST'], | ||
| ['status' => 200, 'body' => '{"success": true}'] | ||
| ); | ||
|
|
||
| $output = $this->runScript('src/notify-webhook'); | ||
|
|
||
| $this->assertStringContainsString('Finished Webhook notification', $output); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Payload/token-focused tests don’t currently assert on the actual webhook body
testSuccessfulNotificationWithCustomPayload(), testTokenReplacementWithSpecialCharacters(), and testAllTokenReplacements() are named and configured as if they validate the payload content (custom JSON and token expansion), but they only assert that the script finished successfully. They never inspect the body sent to the webhook mock.
To make these tests catch regressions in payload/token logic, consider:
- Extending the HTTP mocking layer (similar to the Slack tests) so
mockRequest()or a dedicatedmockRequestPost()variant can capture the outgoing body and optionally invoke a callback, and - Asserting on that body (e.g., checking that
%project%,%label%,%timestamp%,%environment_url%,%login_url%, and%message%are replaced as expected).
If you prefer to keep these high-level, renaming them to emphasize they only assert successful dispatch (not payload shape) would avoid giving a false sense of coverage.
Also applies to: 175-191, 209-221
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyWebhookTest.php lines 51-63: these tests are
named to validate payload/token replacement but only assert a successful run;
update the test to capture and assert the actual HTTP request body sent to the
webhook by extending the test HTTP mock (or adding a mockRequestPost variant) to
record the POST payload or accept a callback, then assert the JSON body contains
the expected expanded tokens (e.g. values for %project%, %label%, %timestamp%,
%environment_url%, %login_url%, %message%); apply the same pattern to the other
affected tests at lines ~175-191 and ~209-221 so they validate payload shape
instead of only success.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (5)
.vortex/tooling/phpcs.xml (2)
17-26: Resolve the redundancy in file specifications (unresolved from past review).Lines 18–24 redundantly enumerate
src/notify-*subdirectories when line 17 already specifiessrc. Since PHPCS scans directories recursively by default, these explicit subdirectory entries are likely unnecessary. Please either remove them or confirm if they serve a specific purpose (e.g., non-recursive scanning, priority ordering).
33-52: Correct glob patterns in exclude-pattern entries (unresolved from past review).The patterns use backslash-escaped dots (
*.Test\.php,*.TestCase\.php) which are not standard PHPCS glob syntax. PHPCS treats these as literal characters rather than escaped dots. Update the patterns to proper glob format:
*.Test\.php→*Test.phpor**/*Test.php(for recursive matching)*.TestCase\.php→*TestCase.phpor**/*TestCase.php*.test→ Clarify intent; this pattern may not match actual test filenames as writtenApply this diff to fix the glob patterns:
<!-- Allow long array lines in tests. --> <rule ref="Drupal.Arrays.Array.LongLineDeclaration"> - <exclude-pattern>*.Test\.php</exclude-pattern> - <exclude-pattern>*.TestCase\.php</exclude-pattern> + <exclude-pattern>*Test.php</exclude-pattern> + <exclude-pattern>*TestCase.php</exclude-pattern> <exclude-pattern>*.test</exclude-pattern> </rule> <!-- Allow missing class description in tests. --> <rule ref="Drupal.Commenting.ClassComment.Missing"> - <exclude-pattern>*.Test\.php</exclude-pattern> - <exclude-pattern>*.TestCase\.php</exclude-pattern> + <exclude-pattern>*Test.php</exclude-pattern> + <exclude-pattern>*TestCase.php</exclude-pattern> <exclude-pattern>*.test</exclude-pattern> </rule> <!-- Allow missing function names in tests. --> <rule ref="Drupal.Commenting.FunctionComment.Missing"> - <exclude-pattern>*.Test\.php</exclude-pattern> - <exclude-pattern>*.TestCase\.php</exclude-pattern> + <exclude-pattern>*Test.php</exclude-pattern> + <exclude-pattern>*TestCase.php</exclude-pattern> <exclude-pattern>*.test</exclude-pattern> </rule>.vortex/tooling/tests/Traits/MockTrait.php (2)
182-192: Docblock still referencesexit_codeinstead ofresult_codeThe past review comment indicated this was addressed, but the docblock at line 189 still says "exit_code: The exit code to set" while the actual array key used in the implementation (line 211) is
result_code.Apply this diff to align the docblock with implementation:
* @param array<int, array{cmd:string, output?: string, result_code?: int, return?: NULL|FALSE}> $responses * Array of responses to return for each passthru call. * Each response should have: + * - cmd: Expected command (required) * - output: The output to echo - * - exit_code: The exit code to set (0 for success). + * - result_code: The exit code to set (optional, default 0) + * - return: Return value (optional, NULL or FALSE) * @param string $namespace * Namespace to mock the functions in (defaults to DrevOps\VortexTooling).
428-441: Missing type validation before array merge inmockRequestMultiple()Past review comments suggested adding validation to ensure
$response['response']is an array before the merge operation at line 433. If a caller passes a non-array value (e.g., string), this will cause a fatal error with "unsupported operand types".Apply this diff to add validation:
protected function mockRequestMultiple(array $responses, string $namespace = 'DrevOps\\VortexTooling'): void { // Normalize responses by applying defaults before storing. $normalized_responses = []; foreach ($responses as $response) { + if (isset($response['response']) && !is_array($response['response'])) { + throw new \InvalidArgumentException('Mocked request response "response" key must be an array.'); + } + /** @var array{url: string, method?: string, response?: array<string, mixed>} $response */ $response['response'] = ($response['response'] ?? []) + [.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
21-38: Helper loading breaks test isolationThe past review comment identified that
helpers.phpis loaded only in the first test (line 30), which breaks test isolation. When running individual tests or with randomized test order, later tests will fail with undefined function errors.Move the helper loading to
setUp():+ protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockRequestGetSuccess(): void { $this->mockRequestGet( 'https://example.com/api', [], 10, ['status' => 200, 'body' => 'success response'] ); - // Load helpers.php AFTER setting up mocks. - require_once __DIR__ . '/../../src/helpers.php'; - $result = \DrevOps\VortexTooling\request_get('https://example.com/api');
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (5)
.vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/tests/Self/MockInfrastructureSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockMailSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Traits/MockTrait.php(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/phpcs.xml.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php.vortex/tooling/tests/Self/MockMailSelfTest.php
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Self/MockRequestSelfTest.php
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml
🧬 Code graph analysis (4)
.vortex/tooling/tests/Self/MockRequestSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)runScript(38-92).vortex/tooling/src/helpers.php (3)
request_get(346-352)request_post(374-381)request(403-460)
.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (3)
mockPassthruAssertAllMocksConsumed(263-265)mockMailAssertAllMocksConsumed(409-411)mockRequestAssertAllMocksConsumed(666-668)
.vortex/tooling/tests/Self/MockMailSelfTest.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (3)
mockMail(399-401)mockMailMultiple(316-389)mockMailAssertAllMocksConsumed(409-411)
.vortex/tooling/tests/Traits/MockTrait.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-43).vortex/tooling/src/helpers.php (2)
fail(265-268)quit(481-483)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: build (0)
- GitHub Check: build (1)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-tooling (8.3)
- GitHub Check: vortex-test-tooling (8.4)
- GitHub Check: vortex-test-docs
🔇 Additional comments (1)
.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php (1)
18-25: LGTM!This test correctly validates that mock consumption assertions are safe to call when no mocks have been registered. The use of
expectNotToPerformAssertions()is appropriate here.
| public function testMockMailSuccess(): void { | ||
| $this->mockMail([ | ||
| 'to' => '[email protected]', | ||
| 'subject' => 'Test Subject', | ||
| 'message' => 'Test Message', | ||
| ]); | ||
|
|
||
| require_once __DIR__ . '/../../src/helpers.php'; | ||
|
|
||
| $result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []); | ||
|
|
||
| $this->assertTrue($result); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helper loading breaks test isolation
Similar to MockRequestSelfTest, this test class loads helpers.php only in the first test (line 25). This breaks test isolation and will cause later tests to fail when run individually or in randomized order.
Move the helper loading to setUp():
+ protected function setUp(): void {
+ parent::setUp();
+ require_once __DIR__ . '/../../src/helpers.php';
+ }
+
public function testMockMailSuccess(): void {
$this->mockMail([
'to' => '[email protected]',
'subject' => 'Test Subject',
'message' => 'Test Message',
]);
- require_once __DIR__ . '/../../src/helpers.php';
-
$result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public function testMockMailSuccess(): void { | |
| $this->mockMail([ | |
| 'to' => '[email protected]', | |
| 'subject' => 'Test Subject', | |
| 'message' => 'Test Message', | |
| ]); | |
| require_once __DIR__ . '/../../src/helpers.php'; | |
| $result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []); | |
| $this->assertTrue($result); | |
| } | |
| protected function setUp(): void { | |
| parent::setUp(); | |
| require_once __DIR__ . '/../../src/helpers.php'; | |
| } | |
| public function testMockMailSuccess(): void { | |
| $this->mockMail([ | |
| 'to' => '[email protected]', | |
| 'subject' => 'Test Subject', | |
| 'message' => 'Test Message', | |
| ]); | |
| $result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []); | |
| $this->assertTrue($result); | |
| } |
🤖 Prompt for AI Agents
.vortex/tooling/tests/Self/MockMailSelfTest.php lines 18-30: the test loads
helpers.php inside the test method which breaks isolation and ordering; move the
require_once __DIR__ . '/../../src/helpers.php' into a setUp() method so helpers
are loaded before each test. Add a public function setUp(): void {
parent::setUp(); require_once __DIR__ . '/../../src/helpers.php'; } (or
equivalent in your test base) so every test runs with helpers available and test
ordering does not affect behavior.
ea5018f to
4272ff9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.vortex/CLAUDE.md (1)
759-812: Update “three systems” test-harness section to include the new tooling subsystemThis section still describes
.vortex/as having “three separate systems” (docs, installer, tests) and the tree under it omits.vortex/tooling/, which contradicts the earlier “four distinct subsystems” description and the new tooling section.Consider:
- Renaming the heading to reflect four subsystems.
- Adding a
tooling/entry (and a brief description) to the tree.- Updating the bullet list of subsystems to include tooling alongside docs, installer, and template tests.
That will keep this overview consistent with the rest of the document and the new tooling package structure.
♻️ Duplicate comments (49)
.vortex/tooling/.gitattributes (1)
1-2: Remove duplicate.gitattributesexport-ignore entryLines 1 and 2 are identical; you only need one of them.
-/.gitattributes export-ignore /.gitattributes export-ignore /.gitignore export-ignore.vortex/tooling/phpcs.xml (2)
35-37: Verify exclude-pattern glob syntax.PHPCS uses shell-style globs, not regex. The backslash-escaped patterns like
*.Test\.phpmay not match as intended. Consider using standard glob syntax like*Test.phpor**/*Test.php.#!/bin/bash # Verify that PHPCS exclude patterns are working as expected cd .vortex/tooling # List test files that should be excluded fd -e php -p 'Test\.php$' tests/ # Run PHPCS on a specific test file to see if rules are excluded vendor/bin/phpcs --standard=phpcs.xml -s tests/Unit/GetenvTest.php 2>&1 | head -20 || echo "PHPCS not available in sandbox"
17-26: Redundant file entries for recursive scanning.PHPCS recursively scans directories by default. Since
<file>src</file>is already listed, the individualsrc/notify-*entries on lines 18-24 are redundant.<file>src</file> -<file>src/notify</file> -<file>src/notify-email</file> -<file>src/notify-github</file> -<file>src/notify-jira</file> -<file>src/notify-newrelic</file> -<file>src/notify-slack</file> -<file>src/notify-webhook</file> <file>playground</file>.vortex/tooling/src/notify-webhook (1)
98-102: Consider filtering out empty headers.When
VORTEX_NOTIFY_WEBHOOK_HEADERScontains trailing pipes or consecutive separators,explode('|', ...)produces empty strings. These pass through torequest()and may confuse downstream handling.Apply this diff:
$response = request($webhook_url, [ 'method' => $webhook_method, - 'headers' => array_map(trim(...), explode('|', $webhook_headers)), + 'headers' => array_filter( + array_map(trim(...), explode('|', $webhook_headers)), + static fn(string $header): bool => $header !== '' + ), 'body' => $webhook_payload, ]);.vortex/tooling/tests/Self/MockRequestSelfTest.php (1)
21-38: Move helpers loading tosetUp()for test isolation.
helpers.phpis only loaded intestMockRequestGetSuccess(). Whilerequire_onceensures it's available for subsequent tests in the same process, this couples the suite to execution order. Tests may fail when run in isolation or with randomized order.class MockRequestSelfTest extends UnitTestCase { + protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockRequestGetSuccess(): void { $this->mockRequestGet( 'https://example.com/api', [], 10, ['status' => 200, 'body' => 'success response'] ); - // Load helpers.php AFTER setting up mocks. - require_once __DIR__ . '/../../src/helpers.php'; - $result = \DrevOps\VortexTooling\request_get('https://example.com/api');.vortex/tooling/tests/Unit/GetenvTest.php (3)
137-146: Unreachable assertion afterexpectException().With
expectException()set andcaptureOutput()rethrowing the exception, theassertStringContainsString()on line 145 is never executed. The test passes only because the expected exception is thrown, not because the output was verified.To actually verify output content, catch the exception manually:
public function testGetenvRequiredWithNoVarsSetFails(): void { $this->mockQuit(1); - $this->expectException(QuitErrorException::class); - - $output = $this->captureOutput(function (): void { - \DrevOps\VortexTooling\getenv_required('UNSET_VAR_1', 'UNSET_VAR_2'); - }); - - $this->assertStringContainsString('Missing required value for UNSET_VAR_1, UNSET_VAR_2', $output); + $output = ''; + try { + $output = $this->captureOutput(function (): void { + \DrevOps\VortexTooling\getenv_required('UNSET_VAR_1', 'UNSET_VAR_2'); + }); + $this->fail('Expected QuitErrorException was not thrown'); + } + catch (QuitErrorException $e) { + // Expected exception. + } + $this->assertStringContainsString('Missing required value for UNSET_VAR_1, UNSET_VAR_2', $output); }
148-160: Same unreachable assertion issue.The
assertStringContainsString()on line 159 is unreachable due to theexpectException()+captureOutput()combination. Apply the same fix pattern as suggested above.
162-171: Same unreachable assertion issue.The
assertStringContainsString()on line 170 is unreachable. Apply the same fix pattern..vortex/tooling/src/notify-slack (2)
132-134: Consider validating Slack response body.Slack webhooks can return HTTP 200 with an error string in the body (e.g.,
"invalid_payload","no_text"). The current check only validates HTTP status, which may mask configuration errors.if ($response['status'] !== 200) { fail('Unable to send notification to Slack. HTTP status: %s', $response['status']); } + +$body = trim((string) ($response['body'] ?? '')); +if ($body !== 'ok' && $body !== '') { + fail('Slack API returned error: %s', $body); +}
111-111: Add error handling forjson_encode()failure.
json_encode()can returnfalseon encoding errors (e.g., malformed UTF-8 from environment variables). The current code passes the result directly torequest_post()without validation.$payload = json_encode($data, JSON_UNESCAPED_SLASHES); +if ($payload === false) { + fail('Failed to JSON-encode Slack payload: %s', json_last_error_msg()); +}.vortex/tooling/src/notify-newrelic (1)
103-116: Critical: Missing error handling when API request fails.If
$response['ok']isfalse(API failure, network error), the code skips the discovery block entirely but continues to line 115, logging "Discovered application ID" with an empty$newrelic_appid. The subsequent deployment POST (line 149) will then fail or send an invalid request.if ($response['ok'] && $response['body']) { $data = json_decode((string) $response['body'], TRUE); if (isset($data['applications'][0]['id'])) { $newrelic_appid = $data['applications'][0]['id']; } if (!$newrelic_appid) { pass('Notification skipped: No New Relic application ID found for %s. This is expected for non-configured environments.', $newrelic_app_name); quit(); } + + pass('Discovered application ID: %s', $newrelic_appid); + } + else { + pass('Notification skipped: Unable to discover New Relic application ID for %s. API request failed.', $newrelic_app_name); + quit(); } - - pass('Discovered application ID: %s', $newrelic_appid); }.vortex/tooling/CLAUDE.md (2)
225-228: Mock method naming inconsistency with actual MockTrait API.Lines 225-228 still reference
mockCurl,mockCurlMultiple, andmockCurlAssertAllMocksConsumed, but the actual MockTrait (and lines 804-809 of this document) usesmockRequest*naming. This creates confusion for developers writing tests.Apply this diff:
-protected function mockCurl(array $response): void -protected function mockCurlMultiple(array $responses): void -protected function mockCurlAssertAllMocksConsumed(): void +protected function mockRequest(string $url, array $options = [], array $response = [], string $namespace = 'DrevOps\\VortexTooling'): void +protected function mockRequestGet(string $url, array $headers = [], int $timeout = 10, array $response = [], string $namespace = 'DrevOps\\VortexTooling'): void +protected function mockRequestPost(string $url, $body = NULL, array $headers = [], int $timeout = 10, array $response = [], string $namespace = 'DrevOps\\VortexTooling'): void
817-819: Wrap bare URLs in markdown links to satisfy linting.Static analysis flags these bare URLs (MD034). Wrap them for consistency and to pass markdownlint:
-## Additional Resources - -- PHPUnit Documentation: https://phpunit.de/documentation.html -- php-mock Documentation: https://github.com/php-mock/php-mock-phpunit -- Vortex Template: https://github.com/drevops/vortex +## Additional Resources + +- PHPUnit Documentation: <https://phpunit.de/documentation.html> +- php-mock Documentation: <https://github.com/php-mock/php-mock-phpunit> +- Vortex Template: <https://github.com/drevops/vortex>.vortex/tooling/src/notify-github (1)
85-88: Consider adding explicit Content-Type header for JSON payloads.Both POST requests send JSON but only specify Accept header. Adding
Content-Type: application/jsonmakes intent explicit.$response = request_post($url, json_encode($payload_data), [ 'Authorization: token ' . $github_token, 'Accept: application/vnd.github.v3+json', + 'Content-Type: application/json', ]);Same applies to line 147-150.
.vortex/tooling/rector.php (2)
38-50: Paths excludesrc/helpers.phpand individual notification scripts.The
withPaths()configuration missessrc/helpers.phpand lists directories likesrc/notifywhich don't exist (scripts are files likesrc/notify-email, not directories). Consider simplifying:->withPaths([ __DIR__ . '/check-no-exit.php', - __DIR__ . '/src/notify', - __DIR__ . '/src/notify-email', - __DIR__ . '/src/notify-github', - __DIR__ . '/src/notify-jira', - __DIR__ . '/src/notify-newrelic', - __DIR__ . '/src/notify-slack', - __DIR__ . '/src/notify-webhook', + __DIR__ . '/src', __DIR__ . '/playground', __DIR__ . '/tests', ])
77-79: PHP version comment doesn't match configuration.Comment states "modernizes syntax to PHP 8.3" but
withPhpSets(php82: TRUE)targets PHP 8.2. Align these to avoid confusion.Either update the comment to say PHP 8.2, or change to
php83: TRUEif 8.3 is intended..github/workflows/vortex-test-tooling.yml (1)
56-58: Add comment explaining PACKAGE_TOKEN to GITHUB_TOKEN mapping.The environment variable
GITHUB_TOKENusessecrets.PACKAGE_TOKENwhich can be confusing. If this is intentional (e.g., for elevated privileges), add a comment to clarify.env: + # Uses PACKAGE_TOKEN for elevated API access (if needed by tests) GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }}.vortex/tooling/src/notify-jira (3)
155-159: ADF doccontentmust be an array of block nodes.The Jira ADF schema requires
contentto be an array.text_to_adf()returns a single paragraph node, but it needs to be wrapped in an array.$comment_body = [ 'type' => 'doc', 'version' => 1, - 'content' => $jira_message_adf, + 'content' => [$jira_message_adf], ];
218-228: Transition request ignores API response.The
request_postcall for transitioning the issue doesn't capture or validate the response. If the transition fails, the script reports success anyway.- request_post($url, json_encode($payload_data), [ + $response = request_post($url, json_encode($payload_data), [ 'Authorization: Basic ' . $token, 'Content-Type: application/json', ]); + if (!$response['ok']) { + fail('Unable to transition issue: %s', $response['body'] ?: 'unknown error'); + } + pass('Transitioned issue to %s ', $jira_transition);
255-268: Assignee update request ignores API response.Similar to the transition request, the assignee PUT request doesn't validate the response. Silent failures make troubleshooting difficult.
- request($url, [ + $response = request($url, [ 'method' => 'PUT', 'body' => json_encode($payload_data), 'headers' => [ 'Authorization: Basic ' . $token, 'Content-Type: application/json', ], ]); + if (!$response['ok']) { + fail('Unable to assign issue: %s', $response['body'] ?: 'unknown error'); + } + pass('Assigned issue to ' . $jira_assignee_email);.vortex/tooling/tests/Unit/TokenTest.php (1)
32-55: Previous feedback on JSON-escaping edge cases still applies.A past review suggested adding data provider scenarios for replacement values containing quotes, backslashes, or newlines to lock in the
json_encode()escaping behavior inreplace_tokens(). The current test cases cover basic scenarios but don't exercise this edge-case behavior..vortex/tooling/src/notify-email (4)
119-133: Reuse a single timestamp for subject and body.The subject (line 122) and message (line 130) each call
date()separately, which can drift by a second between calls. A previous review flagged this; compute the timestamp once and reuse it.+$timestamp = date('d/m/Y H:i:s T'); + $email_subject = replace_tokens($email_subject, [ 'project' => $notify_project, 'label' => $notify_label, - 'timestamp' => date('d/m/Y H:i:s T'), + 'timestamp' => $timestamp, 'environment_url' => $notify_env_url, 'login_url' => $notify_login_url, ]); $email_message = replace_tokens($email_message, [ 'project' => $notify_project, 'label' => $notify_label, - 'timestamp' => date('d/m/Y H:i:s T'), + 'timestamp' => $timestamp, 'environment_url' => $notify_env_url, 'login_url' => $notify_login_url, ]);
159-179: Sanitize header values to prevent email header injection.Values used in email headers (
$email_from,$email_subject,$to, CC/BCC lists) should have CR/LF characters stripped to prevent header injection attacks. While modern PHP'smail()has some protections, explicit sanitization is safer and makes intent clear.A previous review flagged this concern. Consider adding a sanitization helper:
function sanitize_email_header(string $value): string { return str_replace(["\r", "\n", "\0"], '', $value); }Then apply it to
$email_from,$email_subject, and each recipient before use in headers.
181-190: Consider logging failed deliveries for debugging.Only successful sends are tracked in
$sent. Whenmail()returnsFALSE, the failure is silent. A previous review suggested tracking failed recipients for debugging visibility.
90-107: Filter empty entries and escape quotes in display names.Two concerns with
parse_email_recipients():
- After
explode()andtrim(), empty strings from trailing/extra commas will produce invalid addresses.- If
$namecontains double quotes, the formatted address"$name" <$email>will be malformed.function parse_email_recipients(string $recipients): array { if (empty($recipients)) { return []; } $parsed = []; - $recipient_list = array_map(trim(...), explode(',', $recipients)); + $recipient_list = array_filter( + array_map(trim(...), explode(',', $recipients)), + static fn(string $v): bool => $v !== '' + ); foreach ($recipient_list as $recipient) { $parts = array_map(trim(...), explode('|', $recipient, 2)); $email = $parts[0]; $name = $parts[1] ?? ''; - $parsed[] = empty($name) ? $email : sprintf('"%s" <%s>', $name, $email); + $parsed[] = empty($name) ? $email : sprintf('"%s" <%s>', addslashes($name), $email); } return $parsed; }.vortex/tooling/src/notify (2)
54-54: First-class callable syntax requires PHP 8.1+This line uses
trim(...)syntax which requires PHP 8.1 or later. Based on past review comments, this has already been flagged and the composer.json PHP version requirement should be updated accordingly.
66-66: Use DIRECTORY_SEPARATOR for cross-platform compatibilityThe script path construction uses a hardcoded forward slash. Based on past review comments, this should use
DIRECTORY_SEPARATORfor cross-platform compatibility..vortex/tooling/tests/Unit/FormatterTest.php (3)
33-60: Use EnvTrait helpers instead of direct putenvLine 35 uses
putenv('TERM=dumb')directly. Based on past review comments, tests should use$this->envSet('TERM', 'dumb')from EnvTrait to ensure proper tracking and cleanup in tearDown.
62-83: Use EnvTrait helpers instead of direct putenvLine 64 uses
putenv('TERM=xterm-256color')directly. Based on past review comments, this should use$this->envSet('TERM', 'xterm-256color')for proper environment tracking.
100-118: Use EnvTrait helpers instead of direct putenvLines 103 and 106 use
putenv()directly. Based on past review comments, these should use$this->envSet('TERM', $term_value)and$this->envUnset('TERM')from EnvTrait for proper environment management..vortex/tooling/tests/Unit/NotifyGithubTest.php (1)
23-31: Test token value triggers secret scannersLine 24 uses
'ghp_test123456'which resembles a real GitHub Personal Access Token. Based on past review comments, this should be changed to a value that doesn't match PAT patterns (e.g., 'test-token' or 'ghp_TEST_TOKEN') to avoid triggering secret detection tools like Gitleaks..vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
61-107: Output assertions after expectException may still be unreachableBased on past review comments, these tests were flagged because assertions on
$outputafterexpectExceptionare unreachable—once the exception is thrown fromrunScript(), PHPUnit's expectException handler terminates the test method, and the subsequent assertions never execute.The past review indicates this was "Addressed in commit 2cde55e", but the current code still shows the same pattern (expectException followed by runScript followed by output assertions). Please confirm whether the fix was applied and is working as intended, or if these assertions remain unreachable.
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (4)
9-13: Update docblock to referencecheck-no-exit.phpand its behaviorThe class comment still points to
check-exit-usage.phpand a generic “exit() usage checker”, whilesetUp()targetscheck-no-exit.php. Please align the docblock text with the actual script name and purpose to avoid confusion.
29-36: Use unique per-test temp directory and recursivemkdir()
$this->testDiris a fixed path underself::$tmp, andmkdir()is called on it eachsetUp(). This can:
- Emit warnings when directories already exist (especially with
failOnWarning="true"),- Leave files from earlier tests in place, so
testHandlesEmptyDirectories()is not actually testing an empty tree and becomes order‑dependent.Create a unique directory per test and use recursive
mkdir():protected function setUp(): void { parent::setUp(); $this->scriptPath = dirname(__DIR__, 2) . '/check-no-exit.php'; - $this->testDir = self::$tmp . '/check-exit-test'; - mkdir($this->testDir); - mkdir($this->testDir . '/src'); - mkdir($this->testDir . '/tests'); + $this->testDir = self::$tmp . '/check-exit-test-' . uniqid('', true); + mkdir($this->testDir . '/src', 0777, true); + mkdir($this->testDir . '/tests', 0777, true); }This isolates tests and prevents mkdir warnings.
47-50: Refactor duplicatedexec()blocks and escape shell argumentsEach test repeats:
$output = []; $exit_code = 0; exec(sprintf('cd %s && php %s 2>&1', $this->testDir, $this->scriptPath), $output, $exit_code);This:
- Duplicates logic six times, and
- Builds a shell command without escaping
$this->testDir/$this->scriptPath, which is fragile for paths with spaces or special characters.Factor this into a helper that uses
escapeshellarg():/** * Run check-no-exit.php from the current test directory. * * @return array{0: string[], 1: int} */ private function runCheckNoExitScript(): array { $output = []; $exit_code = 0; $cmd = sprintf( 'cd %s && php %s 2>&1', escapeshellarg($this->testDir), escapeshellarg($this->scriptPath), ); exec($cmd, $output, $exit_code); return [$output, $exit_code]; }and then e.g.:
[$output, $exit_code] = $this->runCheckNoExitScript();in each test.
Also applies to: 65-67, 82-84, 103-105, 120-122, 133-135
113-126: Also assert on non‑zero exit code for the line‑number test
testReportsCorrectLineNumbers()only checks that'line-test.php:5'appears in the output. If the script ever logs the correct line but exits with code 0, this test would still pass.Add an exit‑code assertion for parity with your other “exit detected” tests:
$output_str = implode("\n", $output); - $this->assertStringContainsString('line-test.php:5', $output_str); + $this->assertEquals(1, $exit_code); + $this->assertStringContainsString('line-test.php:5', $output_str);.vortex/tooling/tests/Unit/NotifyRouterTest.php (2)
102-103: Assertrealpath()success before using script paths in mocksAll the success‑path tests call
realpath()for notifier scripts and then feed the result intomockPassthru()without verifying it’s non‑false. If a script is missing or misnamed, you’d end up mocking""instead of surfacing a clear failure.Consider asserting that
realpath()resolved correctly before using the path, e.g.:- $script_path = realpath(__DIR__ . '/../../src/notify-email'); + $script_path = realpath(__DIR__ . '/../../src/notify-email'); + $this->assertNotFalse($script_path, 'notify-email script path must resolve via realpath().');and similarly for the Slack/Webhook variants. This makes misconfigured or missing scripts much easier to diagnose in CI.
Also applies to: 119-120, 136-138, 170-172, 201-202, 223-224, 239-240, 255-256, 269-270
219-232: Tighten assertions in default/fallback tests or rename them to match behaviorThe following tests currently only assert that notifications finish successfully:
testDefaultEventTypeIsPostDeployment()testProjectFallbackToGenericVariable()testDefaultLoginUrlGeneration()Given their names, you would expect them to verify:
- That the router actually uses
post_deploymentas the default event,- That the project falls back to
VORTEX_PROJECTwhenVORTEX_NOTIFY_PROJECTis unset,- That the login URL is correctly derived when
VORTEX_NOTIFY_LOGIN_URLis absent.Right now they don’t assert on the resolved event/project/login URL at all, which can give a false sense of coverage.
Either:
- Extend the tests to assert on the effective values (e.g., by checking for specific log lines or markers), or
- Rename them to reflect that they only assert overall dispatch success.
Also applies to: 234-248, 250-264
.vortex/tooling/tests/Unit/NotifyNewrelicTest.php (1)
23-33: Adjust fake New Relic keys to avoid static-analysis “secret” noiseThe hardcoded values:
VORTEX_NOTIFY_NEWRELIC_USER_KEY => 'NRAK-TEST123456'NEWRELIC_USER_KEY => 'NRAK-FALLBACK456'still resemble real New Relic API keys and have been flagged by gitleaks as “Generic API Key”.
To keep security tooling quiet while preserving readability, either:
- Change them to clearly non‑secret placeholders that don’t match common key patterns (e.g.
'NRAK-XXXX-PLACEHOLDER'or'NR-TEST-KEY'), and update the corresponding header expectations, or- Add a narrow allowlist/ignore rule for these exact strings/lines in your gitleaks config.
This avoids recurring CI noise without weakening actual secret scanning.
Also applies to: 223-233
.vortex/tooling/tests/Unit/NotifyWebhookTest.php (1)
51-63: Payload/token‑focused tests should assert on the actual webhook body or be renamedThe tests:
testSuccessfulNotificationWithCustomPayload()testTokenReplacementWithSpecialCharacters()testAllTokenReplacements()are named as if they validate payload content and token replacement, but they only assert that the script finishes successfully (and, in some cases, on high‑level log lines). They never examine the HTTP request body sent via
mockRequest().To make these tests truly cover payload logic, consider:
- Extending the HTTP mocking layer to capture the POST body (or accept a callback) and assert on it, similar to how Slack/New Relic tests inspect JSON payloads, or
- If you prefer to keep them high‑level, renaming the tests to emphasize that they only check successful dispatch, not payload shape.
Also applies to: 175-191, 209-221
.vortex/tooling/tests/Unit/NotifySlackTest.php (2)
33-51: EnsuremockRequestPost()actually exercises the$bodycallbacks used hereAll these tests pass
$this->callback(...)as the second argument tomockRequestPost()and then assert heavily on the decoded JSON payload. However, the currentMockTrait::mockRequestPost()implementation (per the shared snippet) only forwardsurl,method, andresponsetomockRequestMultiple, ignoring$body,$headers, and$timeout.If that’s still the case, the callbacks provided here are never invoked, and none of the payload assertions actually run — the tests only verify that some POST occurred and returned the mocked status code.
Please update the mocking layer so that:
mockRequestPost()includes the provided$body(and headers/timeout) in the request spec passed tomockRequestMultiple, and- The underlying stub invokes the callback with the actual request body when it sees a PHPUnit callback there,
so that these tests truly validate the constructed Slack payload.
Also applies to: 60-76, 84-103, 105-125, 127-151, 168-197, 199-208, 212-228, 230-252
37-38: Fix array‑shape phpdoc syntax so static analysis tools understand payload structureAnnotations like:
/** @var array {username: string, icon_emoji: string, attachments: array} $payload */use
array {…}instead of the standard shape syntaxarray{…}expected by PHPStan/Psalm.Update them to valid array‑shape syntax, for example:
-/** @var array {username: string, icon_emoji: string, attachments: array} $payload */ +/** @var array{username: string, icon_emoji: string, attachments: array} $payload */(and similarly for the other occurrences). This lets static analysis and IDEs correctly interpret the payload structure.
Also applies to: 66-67, 90-91, 111-112, 136-137
.vortex/tooling/tests/Self/MockMailSelfTest.php (1)
18-30: Loadhelpers.phpinsetUp()instead of only in the first testCurrently only
testMockMailSuccess()does:require_once __DIR__ . '/../../src/helpers.php';while all tests call
\DrevOps\VortexTooling\mail(). This breaks test isolation: running any later test alone (or with different ordering) will fail if helpers.php hasn’t been required yet.Move the helper loading into a
setUp()method and drop the inline require:class MockMailSelfTest extends UnitTestCase { + protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockMailSuccess(): void { $this->mockMail([ 'to' => '[email protected]', 'subject' => 'Test Subject', 'message' => 'Test Message', ]); - - require_once __DIR__ . '/../../src/helpers.php'; - $result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []);This ensures every test has the helpers available, regardless of execution order.
.vortex/tooling/src/helpers.php (3)
35-57: Hardenexecute_override()against path traversal and unsafe script names
$pathis concatenated directly into$full_pathand then passed topassthru(). If a caller ever passes something other than a simple file name (e.g.../bin/evil.sh), you can traverse out ofVORTEX_TOOLING_CUSTOM_DIRand execute arbitrary files.Consider validating
$pathbefore use so it’s a plain name with no separators or..:function execute_override(string $path): void { - $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); - if ($dir) { - if (!is_dir($dir)) { - fail('Custom directory specified in VORTEX_TOOLING_CUSTOM_DIR does not exist: %s', $dir); - } - - $full_path = $dir . DIRECTORY_SEPARATOR . $path; - if (file_exists($full_path) && is_executable($full_path)) { - passthru(sprintf('"%s"', $full_path), $exit_code); - quit($exit_code); - } - } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR'); + if (!$dir) { + return; + } + + if (!is_dir($dir)) { + fail('Custom directory specified in VORTEX_TOOLING_CUSTOM_DIR does not exist: %s', $dir); + } + + // Prevent path traversal by requiring a simple file name. + if (basename($path) !== $path || str_contains($path, '..')) { + fail('Invalid script name for override: %s', $path); + } + + $full_path = $dir . DIRECTORY_SEPARATOR . $path; + if (file_exists($full_path) && is_executable($full_path)) { + passthru(sprintf('"%s"', $full_path), $exit_code); + quit($exit_code); + } }
283-288: Escape$commandincommand_exists()to avoid shell‑injection risk
exec(sprintf('command -v %s 2>/dev/null', $command), ...)interpolates$commanddirectly into a shell string. If$commandis ever influenced by user input, this can be used for command injection.Use
escapeshellarg()when composing the shell command:function command_exists(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }
403-460: Add error handling forcurl_init()andcurl_setopt_array()failures inrequest()
curl_init($url)may returnfalse, andcurl_setopt_array()may fail. In both cases the code currently continues, which can lead to aTypeError(passingfalseas a CurlHandle) or a misconfigured request with confusing downstream errors.Consider failing early when either step fails:
function request(string $url, array $options = []): array { @@ - $ch = curl_init($url); + $ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + } @@ - curl_setopt_array($ch, $opts); + if (curl_setopt_array($ch, $opts) === FALSE) { + // Try to surface a useful error message if available. + $error_msg = function_exists('curl_error') ? curl_error($ch) : 'unknown cURL error'; + fail('Failed to set curl options for URL %s: %s', $url, $error_msg); + } @@ - } - finally { - // CurlHandle objects are automatically freed when they go out of scope - // (PHP 8.0+), so explicit curl_close() is no longer needed. - // The unset here ensures the handle goes out of scope immediately. - unset($ch); - } + } + finally { + // CurlHandle objects are automatically freed when they go out of scope + // (PHP 8.0+), so explicit curl_close() is no longer needed. + // The unset here ensures the handle goes out of scope immediately. + unset($ch); + } }.vortex/tooling/tests/Unit/NotifyJiraTest.php (1)
35-131: Body‑inspection callbacks depend on MockTrait support that isn’t implemented yetTests like
testSuccessfulNotificationCommentOnly()andtestSuccessfulNotificationWithTransition()pass$this->callback(...)as the second argument tomockRequestPost()to inspect the JSON payload. However, the currentMockTrait::mockRequestPost()implementation ignores its$bodyparameter and only enqueues URL/method/response, so these callbacks are never actually evaluated.Until
MockTrait::mockRequestPost()/mockRequestMultiple()are extended to capture the request body fromcurl_setopt_array()and invoke the configured matcher, these tests validate URLs and status codes but not the actual Jira payload structure..vortex/tooling/tests/Traits/MockTrait.php (2)
183-195: Align passthru mock docblock with actual response shape (result_code, notexit_code)The docblock for
mockPassthruMultiple()still refers to anexit_codekey, but the type and implementation useresult_code. This mismatch can confuse callers constructing responses.Suggest updating the narrative to match the actual shape, e.g.:
- * Each response should have: - * - output: The output to echo - * - exit_code: The exit code to set (0 for success). + * Each response should have: + * - cmd: Expected command (required) + * - output: Output to echo (optional) + * - result_code: Exit code to set (optional, defaults to 0) + * - return: Return value (optional, NULL or FALSE)
413-657:mockRequestPost()ignores$body, so request‑payload assertions in tests never runThe HTTP‑request mocking stack works off a per‑request response queue, but it currently never exposes or validates the request body:
mockRequestMultiple()storesurl, optionalmethod, and normalizedresponse, but no body or matcher.curl_setopt_array’s mock only recordsCURLOPT_CUSTOMREQUESTinto$current_method; it doesn’t captureCURLOPT_POSTFIELDS.curl_exec’s mock reads$mock['response']['body']and returns it, but never inspects the outgoing payload.mockRequestPost()accepts$bodybut discards it when callingmockRequestMultiple().As a result, tests that pass callbacks in
$body(e.g., the Jira tests using$this->callback(function ($body) { … })) never have those callbacks invoked, so payload structure/content isn’t actually verified.Consider wiring body validation through the mock layer, for example:
- In
mockRequestMultiple(), track an optionalbody/bodyMatcherfield alongsideurlandmethod.- In the mocked
curl_setopt_array(), captureCURLOPT_POSTFIELDSinto a$current_bodyvariable.- In
curl_exec()’s callback, after validating URL/method, if abodyMatcheris present:
- If it’s a callable, invoke it with
$current_body.- Or, if it’s a literal expected string, compare it directly to
$current_bodyand throw on mismatch.- In
mockRequestPost()/mockRequest(), pass the$body(or a matcher closure) into that field instead of dropping it.That will make the notifier tests’ payload assertions meaningful and avoid a false sense of coverage.
| ├── composer.json # Package configuration | ||
| ├── phpunit.xml # PHPUnit configuration | ||
| ├── CLAUDE.md # Development guide | ||
| ├── scripts_conversion_plan.md # This file | ||
| ├── src/ | ||
| │ ├── helpers.php # ✅ Core helper functions (17 functions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Optional: address markdownlint bare-URL warning
markdownlint is flagging a bare URL (e.g., https://www.vortextemplate.com). If you want a clean lint run, wrap it as a proper Markdown/autolink, for example:
Published to <https://www.vortextemplate.com>or
Published to [vortextemplate.com](https://www.vortextemplate.com)🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
47-47: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents
In .vortex/tooling/scripts_conversion_plan.md around lines 44 to 49 there is a
bare URL flagged by markdownlint; update the markdown to avoid a bare URL by
converting it to an autolink (<https://www.vortextemplate.com>) or a named link
([vortextemplate.com](https://www.vortextemplate.com)) wherever the URL appears
so the linter no longer flags it.
| ]; | ||
|
|
||
| $url = sprintf('%s/applications/%s/deployments.json', $newrelic_endpoint, $newrelic_appid); | ||
| $response = request_post($url, json_encode($payload_data, JSON_UNESCAPED_SLASHES), [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for json_encode() failure.
Similar to the Slack notifier, json_encode() can return false on encoding errors. The result is passed directly to request_post() without validation.
-$response = request_post($url, json_encode($payload_data, JSON_UNESCAPED_SLASHES), [
+$payload = json_encode($payload_data, JSON_UNESCAPED_SLASHES);
+if ($payload === false) {
+ fail('Failed to JSON-encode New Relic payload: %s', json_last_error_msg());
+}
+
+$response = request_post($url, $payload, [
'Api-Key: ' . $newrelic_user_key,
'Content-Type: application/json',
]);🤖 Prompt for AI Agents
In .vortex/tooling/src/notify-newrelic around line 150, the code calls
json_encode() inline and passes its result directly to request_post() which can
receive false on encoding failure; change this to first assign json_encode(...)
to a variable, check if the result is === false, and on failure log or handle
the error (include json_last_error_msg() in the log) and abort/return an error
before calling request_post(); only call request_post() with the validated
encoded string when json_encode() succeeded.
| public function testExecuteOverrideWithFileInsteadOfDir(): void { | ||
| $custom_dir = self::$tmp . '/custom'; | ||
| File::dump($custom_dir); | ||
| $custom_dir = self::$tmp . '/custom'; | ||
|
|
||
| $this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir); | ||
|
|
||
| $script_path = $custom_dir . '/test-override-passing'; | ||
| file_put_contents($script_path, "#!/usr/bin/env php\n<?php\necho 'override executed';"); | ||
| chmod($script_path, 0755); | ||
|
|
||
| $this->runScriptError('tests/Fixtures/test-override-passing', sprintf('Custom directory specified in VORTEX_TOOLING_CUSTOM_DIR does not exist: %s', $custom_dir)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid creating a file under a path that is itself a file in testExecuteOverrideWithFileInsteadOfDir()
Here:
$custom_dir = self::$tmp . '/custom';
File::dump($custom_dir); // creates a file at $custom_dir
$custom_dir = self::$tmp . '/custom';
$this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);
$script_path = $custom_dir . '/test-override-passing';
file_put_contents($script_path, "#!/usr/bin/env php\n<?php\necho 'override executed';");
chmod($script_path, 0755);File::dump($custom_dir) makes $custom_dir a file. Writing $custom_dir . '/test-override-passing' then targets a child path under a file, which can trigger filesystem warnings and is not needed for this scenario.
Since execute_override() should fail as soon as it sees that VORTEX_TOOLING_CUSTOM_DIR is not a directory, you can safely drop the script creation:
$custom_dir = self::$tmp . '/custom';
File::dump($custom_dir);
$custom_dir = self::$tmp . '/custom';
$this->envSet('VORTEX_TOOLING_CUSTOM_DIR', $custom_dir);
-
- $script_path = $custom_dir . '/test-override-passing';
- file_put_contents($script_path, "#!/usr/bin/env php\n<?php\necho 'override executed';");
- chmod($script_path, 0755);This keeps the test focused and avoids spurious I/O warnings.
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/OverrideTest.php lines 106-118: the test creates a
file at the custom-dir path with File::dump(), then attempts to create a child
script under that path which is invalid because the parent is a file; remove the
creation of the child script (file_put_contents and chmod for $script_path) so
the test only sets VORTEX_TOOLING_CUSTOM_DIR to a file and asserts
execute_override() fails due to the path not being a directory.
- Documented all 17 helper functions (486 lines) - Documented completed notify router (80 lines, 20 tests) - Documented all 6 notification channel scripts (1,097 lines, 130 tests) - Added current test statistics (247 tests, 1,258 assertions) - Updated package structure with all completed files - Added detailed feature lists for each notify script - Documented test coverage for each component - Added key learnings and best practices sections - Updated timeline estimates based on progress - Included conversion patterns and coding standards
- Added mail mock infrastructure to MockTrait with mockMail(), mockMailMultiple(), and validation - Converted src/notify-email from system mail/sendmail commands to PHP's native mail() function - Reduced notify-email from 172 to 133 lines by removing command availability checking - Rewrote NotifyEmailTest.php with 16 comprehensive tests using the new mail mock - Fixed helpers.php replace_tokens() to use JSON_UNESCAPED_SLASHES preventing URL escaping - All 250 tests pass with 1,260 assertions and 100% code coverage maintained
- Added VORTEX_NOTIFY_EMAIL_CC and VORTEX_NOTIFY_EMAIL_BCC environment variables - Updated mail mock infrastructure to validate headers (both array and string formats) - Parse CC/BCC recipients with same format as TO recipients (comma-separated with optional names) - Added Cc and Bcc headers to mail() calls when CC/BCC recipients are specified - Display CC/BCC in notification summary output - Added 8 comprehensive tests covering single/multiple CC/BCC scenarios - All 257 tests pass with 1,283 assertions and 100% code coverage maintained
4272ff9 to
fddfa28
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 12
♻️ Duplicate comments (46)
.vortex/tooling/phpcs.xml (1)
34-52: Re‑verifyexclude-patternglobs actually match your test filesThe
exclude-patternentries like*.Test\.php,*.TestCase\.php, and*.testare intended to relax rules for tests, but PHPCS patterns are glob‑style, not regex, so backslash‑escaping dots will not behave like a regex and may fail to match*Test.php/*TestCase.phpas expected. It’s worth double‑checking that these patterns really hit yourtests/**files and, if not, switching to plain glob forms such as*Test.php,*TestCase.php, or**/*Test.php.You can confirm by running PHPCS against a known test file and checking whether the Drupal sniffs are actually skipped there.
.vortex/tooling/scripts_conversion_plan.md (1)
576-618: Keep composer.json snippet in sync with the actual tooling manifest (and optionally clean up markdownlint noise)The “Package Setup (composer.json)” block is labeled as the “Current configuration” but it no longer matches
.vortex/tooling/composer.json(e.g. it omitsdealerdirect/phpcodesniffer-composer-installer,ergebnis/composer-normalize,squizlabs/php_codesniffer, and theconfig.allow-pluginssection). Either update the snippet to mirror the real file or explicitly mark it as a trimmed example to avoid confusing readers.Separately, markdownlint still reports MD0xx issues here (blank lines around headings/fences, ordered list prefixes). If you want a clean lint run, you can re-run the repo’s markdown formatter (e.g.,
cd .vortex && ahoy lint-markdown-fix) or adjust the headings and fenced blocks manually..vortex/tooling/CLAUDE.md (1)
47-99: Sync documented helper API (validation/env helpers) withhelpers.php, and optionally tidy markdownlint issuesThe “Core Helper Functions” section still documents
validate_variable()/validate_command()as the validation API, while the conversion plan and helpers summary describegetenv_default(),getenv_required(),command_exists(), andterm_supports_color()as the canonical helpers. To avoid confusion for contributors, I’d align this section with whateversrc/helpers.phpactually exposes now (includingexecute_override(), the getenv helpers, and validation helpers), and drop or clearly mark any legacy names if they’re no longer part of the public surface.Separately, markdownlint still flags a few minor issues here (blank lines around fenced blocks and headings, ordered list numbering, and bare URLs for PHPUnit/php-mock/Vortex links). If CI cares about markdownlint, wrapping those URLs in
<...>/links and adding blank lines around code fences will clear the warnings..vortex/tooling/.gitattributes (1)
1-2: Remove duplicate.gitattributesentry.Lines 1 and 2 both contain the identical entry
/.gitattributes export-ignore. One of these duplicate lines should be removed..vortex/tooling/src/notify-webhook (2)
16-17: Remove blank lines after separator comments to satisfy PHPCS.The
// -----------------------------------------------------------------------------separators are immediately followed by blank lines, which PHPCS flags (Drupal.Commenting.InlineComment.SpacingAfter). Remove the blank lines on Lines 17 and 64 so the separator comments are directly followed by code or the next comment.Also applies to: 63-64
98-102: Filter out empty headers when parsingVORTEX_NOTIFY_WEBHOOK_HEADERS.
explode('|', $webhook_headers)can yield empty strings (e.g., trailing or double pipes), which then get passed torequest()as blank headers. Cheaply trim and filter out empty values before sending the request.-$response = request($webhook_url, [ - 'method' => $webhook_method, - 'headers' => array_map(trim(...), explode('|', $webhook_headers)), - 'body' => $webhook_payload, -]); +$response = request($webhook_url, [ + 'method' => $webhook_method, + 'headers' => array_filter( + array_map(trim(...), explode('|', $webhook_headers)), + static fn(string $header): bool => $header !== '' + ), + 'body' => $webhook_payload, +]);.vortex/tooling/src/notify-github (2)
64-65: Consider relaxing the upper bound on deployment ID length.The hard limit of 11 digits may reject future larger GitHub deployment IDs. While the numeric check is valuable, consider removing the upper bound constraint or making it more generous.
85-88: Missing Content-Type header in JSON POST requests.Both deployment creation (line 85) and status update (line 147) POST requests send JSON bodies but omit the
Content-Type: application/jsonheader. While GitHub's API may handle this, setting it explicitly is best practice.Also applies to: 147-150
.github/workflows/vortex-test-tooling.yml (1)
57-57: GITHUB_TOKEN environment variable uses PACKAGE_TOKEN secret.The workflow maps
secrets.PACKAGE_TOKENtoGITHUB_TOKEN, which may be confusing. Consider documenting why elevated privileges are needed or renaming for clarity..vortex/tooling/tests/Self/MockQuitSelfTest.php (1)
61-107: Output assertions after expectException are unreachable.In script-based tests (lines 61-107), assertions on
$output(lines 69-70, 81-82, 93-94, 105-106) will never execute because PHPUnit'sexpectExceptioncauses the test to terminate when the exception is thrown fromrunScript().While a past comment indicated this was addressed in commit 2cde55e, the pattern still appears present. To validate both the exception and output, wrap
runScript()in a manualtry/catchblock instead of usingexpectException..vortex/tooling/playground/env.php (1)
74-91: Consider expanding test coverage for additional variable types.The demo could verify additional variable types like PLAYGROUND_MULTIWORD, PLAYGROUND_NUMERIC, and PLAYGROUND_BOOL_LIKE to demonstrate quoting, numeric, and boolean-like parsing. This would strengthen the demo coverage.
.vortex/tooling/tests/Unit/TokenTest.php (1)
32-55: Add test cases for JSON-escaping edge values.The data provider lacks scenarios testing special characters that require JSON escaping (quotes, backslashes, newlines). Adding these would help catch regressions in the
json_encode()escaping logic..vortex/tooling/src/notify (2)
54-54: First-class callable syntax requires PHP 8.1+This uses
trim(...)first-class callable syntax introduced in PHP 8.1. Ensure the package'scomposer.jsondeclares"php": ">=8.1"in therequiresection to prevent installation on incompatible runtimes.
66-66: Consider usingDIRECTORY_SEPARATORfor cross-platform compatibility.The script path uses a hardcoded forward slash which may cause issues on Windows.
- $script = __DIR__ . ('/notify-' . $channel); + $script = __DIR__ . DIRECTORY_SEPARATOR . 'notify-' . $channel;.vortex/tooling/src/notify-jira (4)
168-168: Logging user email may expose PII.
note('User email : ' . $jira_user_email);logs a concrete user identifier. Depending on compliance requirements (GDPR, etc.), consider masking or omitting this from standard output.
220-227: Transition request ignores API response.The
request_postcall for transitioning the issue doesn't check the response. If Jira returns a 4xx/5xx error, the script will incorrectly report success. Consider validating$response['ok']before logging success.
257-268: Assignee PUT request ignores API response.Similar to the transition request, the assignee update via
request()doesn't validate the response. The script will report success even if the API call failed.
155-159: ADFcontentmust be an array of block nodes.The Jira ADF schema requires
contentat the document level to be an array of block nodes. Currently,$jira_message_adf(a single paragraph node) is assigned directly, which violates the schema and may cause API rejections.$comment_body = [ 'type' => 'doc', 'version' => 1, - 'content' => $jira_message_adf, + 'content' => [$jira_message_adf], ];.vortex/tooling/rector.php (1)
38-79: Past review concerns about Rector paths and PHP version remain unaddressed.The earlier review identified two issues:
withPaths()excludessrc/helpers.php, which exists and should be modernized- Comments reference PHP 8.3 while the configuration uses
php82: TRUEThese concerns were marked as addressed but the code has not changed. Please either:
- Update
withPaths()to[__DIR__ . '/src', __DIR__ . '/tests']to include all source files- Align the PHP version by updating the comment to say "PHP 8.2" or changing the config to
php83: TRUE.vortex/tooling/tests/Unit/NotifyGithubTest.php (1)
24-24: Test token still triggers Gitleaks secret detection.The hardcoded value
'ghp_test123456'continues to match GitHub's PAT prefix pattern and is flagged by static analysis. The earlier review suggested using a clearly fake value (e.g.,'test-token'or'ghp_TEST_TOKEN') to avoid tripping secret scanners while keeping the test valid..vortex/tooling/tests/Unit/FormatterTest.php (5)
35-35: Directputenv()call bypasses EnvTrait cleanup.The earlier review identified that
putenv('TERM=dumb')should be replaced with$this->envSet('TERM', 'dumb')to ensure proper cleanup via EnvTrait. This change was marked as addressed but the code still usesputenv()directly.
64-64: Directputenv()call bypasses EnvTrait cleanup.Similar to line 35, this
putenv('TERM=xterm-256color')should use$this->envSet('TERM', 'xterm-256color')instead.
102-107: Directputenv()calls bypass EnvTrait cleanup.Lines 103 and 106 use
putenv('TERM')andputenv('TERM=' . $term_value)directly. Replace with:
$this->envUnset('TERM')when$term_value === FALSE$this->envSet('TERM', $term_value)otherwise
77-82: Missing test coverage forfail_no_exitwith color support.The data provider
dataProviderOutputFormattersWithColoromits thefail_no_exitformatter, which supports colored output (red ANSI codes). This was noted in an earlier review as incomplete test coverage.
113-117: Missing positive test case for color support.The data provider
dataProviderTermSupportsColoronly includes cases where color support isFALSE(dumb terminal, no terminal). A positive case (e.g.,'xterm-256color' => [..., 'expected' => TRUE]) is needed to verify thatterm_supports_color()correctly returnsTRUEfor color-capable terminals..vortex/tooling/tests/Self/CheckNoExitSelfTest.php (3)
29-36: Fixed temporary directory path causes mkdir warnings and test isolation issues.The earlier review identified that
setUp()reuses a fixed$testDirpath, leading to:
mkdir()warnings on subsequent test runs (which fail tests due tofailOnWarning="true")- File persistence between tests causing order-dependency
The suggested fix was to use
uniqid()and recursivemkdir():$this->testDir = self::$tmp . '/check-exit-test-' . uniqid('', true); mkdir($this->testDir . '/src', 0777, true); mkdir($this->testDir . '/tests', 0777, true);
47-50: Shell arguments not escaped inexec()calls.The
exec()command builds a shell string without escaping$this->testDiror$this->scriptPath. The earlier review suggested creating a helper method usingescapeshellarg()to avoid issues with paths containing spaces or special characters. This pattern repeats at lines 65-68, 82-84, 103-105, 120-122, and 133-135.
113-126: Missing exit code assertion in line-number test.The test
testReportsCorrectLineNumbers()verifies that the output contains'line-test.php:5'but doesn't assert that$exit_codeis non-zero. The earlier review noted this should match the pattern used in other "exit detected" tests..vortex/tooling/tests/Unit/OverrideTest.php (1)
106-118: Avoid creating a child path under a file.The earlier review identified that after
File::dump($custom_dir)makes$custom_dira file, attempting to create$custom_dir . '/test-override-passing'targets a child path under a file, which triggers filesystem warnings. Sinceexecute_override()should fail when it detects the custom dir is not a directory, the script creation (lines 113-115) can be removed..vortex/tooling/src/notify-newrelic (2)
103-107: Missing error handling forjson_decode().The earlier review noted that line 104 calls
json_decode()without validating the result. If the API response contains invalid JSON,$datacould benull, leading to issues when accessing$data['applications'][0]['id']. The suggested fix was to check$data && isset($data['applications'][0]['id'])before accessing array keys.
150-153: Missing error handling forjson_encode().Line 150 calls
json_encode()inline and passes the result directly torequest_post(). If encoding fails,json_encode()returnsfalse, which would be passed as the request body. The earlier review suggested assigning the result to a variable, checking for=== false, and handling the error withjson_last_error_msg()before callingrequest_post()..vortex/tooling/tests/Unit/NotifyNewrelicTest.php (2)
25-25: Test New Relic keys trigger static analysis warnings.The hardcoded value
'NRAK-TEST123456'matches New Relic API key patterns and is flagged by Gitleaks. The earlier review suggested using clearly non-secret placeholder patterns (e.g.,'NRAK-XXXX-PLACEHOLDER'or'NRAK-00000000000000000000') or adding a targeted allowlist rule to prevent CI noise.
232-232: Test New Relic keys trigger static analysis warnings.Similar to line 25,
'NRAK-FALLBACK456'matches New Relic API key patterns. Consider using a non-matching placeholder pattern..vortex/tooling/tests/Unit/NotifyWebhookTest.php (1)
51-63: Payload/token-focused tests don't assert on the actual webhook body.These tests (
testSuccessfulNotificationWithCustomPayload,testTokenReplacementWithSpecialCharacters,testAllTokenReplacements) only verify successful script completion but don't validate that the payload content or token replacements are correct. Consider extendingmockRequest()to capture and verify the outgoing request body.Also applies to: 175-191, 209-221
.vortex/tooling/tests/Unit/NotifyRouterTest.php (3)
48-62: Event type coverage is good, but the "default is post_deployment" test only asserts success.The invalid event type data provider (Lines 55-62) covers a good range, but
testDefaultEventTypeIsPostDeployment()(Lines 219-232) doesn't verify that the default event used is actuallypost_deployment. Consider asserting on the resolved event value in the output.
87-97: Consider assertingrealpath()success before use.If
notify-emailscript were missing or misnamed,realpath()would returnfalse, causing the mock to be set up with an empty string command. This could lead to confusing test failures.
234-264: Fallback/login tests don't validate the actual fallback behavior.
testProjectFallbackToGenericVariable()andtestDefaultLoginUrlGeneration()only assert that notifications "Finished dispatching" but don't verify that the router actually used the generic project variable or derived login URL. Consider asserting on output containing the expected fallback values..vortex/tooling/tests/Unit/NotifySlackTest.php (2)
33-58: ThemockRequestPost()$bodycallback is not consumed by the mock implementation.The
MockTrait::mockRequestPost()method (shown in relevant snippets) only forwardsurl,method, andresponsetomockRequestMultiple(). The$bodycallback argument (Lines 36-46) is never invoked, so these payload assertions do not actually run. The mocking layer needs to be updated to propagate and invoke the body callback.
36-48: Fix array-shape phpdoc syntax for static analysis compatibility.The annotations use
array {…}instead ofarray{…}. Tools like PHPStan/Psalm expect the latter syntax without a space.Also applies to: 65-72, 88-93, 110-114, 135-140
.vortex/tooling/tests/Self/MockMailSelfTest.php (1)
18-30: Helper loading breaks test isolationThe
require_onceforhelpers.phpis only in the first test method (line 25). Other tests liketestMockMailWithHeadersSuccesscall\DrevOps\VortexTooling\mail()without loading the helpers first. If tests run in isolation or randomized order, they will fail because themail()function won't be defined.Move the helper loading to
setUp():+ protected function setUp(): void { + parent::setUp(); + require_once __DIR__ . '/../../src/helpers.php'; + } + public function testMockMailSuccess(): void { $this->mockMail([ 'to' => '[email protected]', 'subject' => 'Test Subject', 'message' => 'Test Message', ]); - require_once __DIR__ . '/../../src/helpers.php'; - $result = \DrevOps\VortexTooling\mail('[email protected]', 'Test Subject', 'Test Message', []);.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
35-73: Callbacks passed tomockRequestPost()are not validated by the mock layerThe callback passed as
$bodyargument (lines 50-56) is intended to validate the request payload, but based on theMockTrait::mockRequestPost()implementation shown in the relevant code snippets, the$bodyparameter is not forwarded tomockRequestMultiple(). The mock only storesurl,method, andresponse.As a result, the payload assertions inside the callback (lines 52-54) never execute, and the test doesn't actually validate the comment body structure.
Consider either:
- Extending
mockRequestPost()to support a body matcher/validator- Removing the callbacks since they provide false confidence
- Using a different approach to validate request payloads
333-362: Custom message test doesn't verify the rendered messageThis test sets
VORTEX_NOTIFY_JIRA_MESSAGEto a templated string but only asserts that the notification finished successfully. It doesn't verify that the custom message was actually rendered with the tokens replaced and used in the Jira comment.Consider asserting on the output or extending the mock to capture and validate the rendered message content.
.vortex/tooling/src/helpers.php (3)
44-57: Add path traversal protection toexecute_override()The
$pathparameter is concatenated directly into a file path without validation. While current usage may pass safe values, this creates a potential path traversal vulnerability if$pathever comes from less trusted sources.Consider validating that
$pathdoesn't contain path traversal patterns:function execute_override(string $path): void { + // Prevent path traversal. + if (str_contains($path, '..') || str_starts_with($path, '/') || str_starts_with($path, '\\')) { + fail('Invalid script path for override: %s', $path); + } + $dir = getenv('VORTEX_TOOLING_CUSTOM_DIR');
283-288: Shell-escape$commandincommand_exists()The
$commandparameter is interpolated directly into a shell string without escaping. While current usage may pass only safe constants, this is a command injection risk if the function is ever called with user-influenced input.function command_exists(string $command): void { - exec(sprintf('command -v %s 2>/dev/null', $command), $output, $code); + exec(sprintf('command -v %s 2>/dev/null', escapeshellarg($command)), $output, $code); if ($code !== 0) { fail(sprintf("Command '%s' is not available", $command)); } }
409-431: Checkcurl_init()andcurl_setopt_array()return values
curl_init()can returnfalseon failure (line 409), and passingfalseto subsequent curl functions would cause a TypeError. Additionally,curl_setopt_array()(line 431) can returnfalseif setting options fails.$ch = curl_init($url); + if ($ch === FALSE) { + fail('Failed to initialize curl for URL: %s', $url); + } try { /** @var array<int, mixed> $opts */ $opts = [ // ... ]; - curl_setopt_array($ch, $opts); + if (!curl_setopt_array($ch, $opts)) { + fail('Failed to set curl options: %s', curl_error($ch)); + }.vortex/tooling/tests/Traits/MockTrait.php (1)
182-195: Docblock mentions 'exit_code' but actual key is 'result_code'The docblock narrative at line 189 says "exit_code: The exit code to set" but the actual array shape at line 185 shows
result_code. This inconsistency could confuse developers.* @param array<int, array{cmd:string, output?: string, result_code?: int, return?: NULL|FALSE}> $responses * Array of responses to return for each passthru call. * Each response should have: - * - output: The output to echo - * - exit_code: The exit code to set (0 for success). + * - cmd: Expected command (required) + * - output: The output to echo (optional) + * - result_code: The exit code to set, 0 for success (optional) + * - return: Return value, NULL or FALSE (optional)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (64)
.github/workflows/vortex-test-tooling.yml(1 hunks).vortex/CLAUDE.md(9 hunks).vortex/tooling/.gitattributes(1 hunks).vortex/tooling/.gitignore(1 hunks).vortex/tooling/CLAUDE.md(1 hunks).vortex/tooling/check-no-exit.php(1 hunks).vortex/tooling/composer.json(1 hunks).vortex/tooling/phpcs.xml(1 hunks).vortex/tooling/phpstan.neon(1 hunks).vortex/tooling/phpunit.xml(1 hunks).vortex/tooling/playground/.env(1 hunks).vortex/tooling/playground/.env.local(1 hunks).vortex/tooling/playground/env.php(1 hunks).vortex/tooling/playground/formatters.php(1 hunks).vortex/tooling/playground/request.php(1 hunks).vortex/tooling/rector.php(1 hunks).vortex/tooling/scripts_conversion_plan.md(1 hunks).vortex/tooling/src/helpers.php(1 hunks).vortex/tooling/src/notify(1 hunks).vortex/tooling/src/notify-email(1 hunks).vortex/tooling/src/notify-github(1 hunks).vortex/tooling/src/notify-jira(1 hunks).vortex/tooling/src/notify-newrelic(1 hunks).vortex/tooling/src/notify-slack(1 hunks).vortex/tooling/src/notify-webhook(1 hunks).vortex/tooling/tests/Exceptions/QuitErrorException.php(1 hunks).vortex/tooling/tests/Exceptions/QuitSuccessException.php(1 hunks).vortex/tooling/tests/Fixtures/check-exit/with-exit-in-comments.php(1 hunks).vortex/tooling/tests/Fixtures/check-exit/with-exit.php(1 hunks).vortex/tooling/tests/Fixtures/check-exit/with-quit.php(1 hunks).vortex/tooling/tests/Fixtures/test-override-failing(1 hunks).vortex/tooling/tests/Fixtures/test-override-passing(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-failing(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-passthru-passing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-failing(1 hunks).vortex/tooling/tests/Fixtures/test-quit-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-get-passing(1 hunks).vortex/tooling/tests/Fixtures/test-request-multiple(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-failing(1 hunks).vortex/tooling/tests/Fixtures/test-request-post-passing(1 hunks).vortex/tooling/tests/Self/CheckNoExitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockInfrastructureSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockMailSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockPassthruSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockQuitSelfTest.php(1 hunks).vortex/tooling/tests/Self/MockRequestSelfTest.php(1 hunks).vortex/tooling/tests/Traits/MockTrait.php(1 hunks).vortex/tooling/tests/Unit/CommandExistsTest.php(1 hunks).vortex/tooling/tests/Unit/DotenvTest.php(1 hunks).vortex/tooling/tests/Unit/FormatterTest.php(1 hunks).vortex/tooling/tests/Unit/GetenvTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyEmailTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyGithubTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyJiraTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyNewrelicTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyRouterTest.php(1 hunks).vortex/tooling/tests/Unit/NotifySlackTest.php(1 hunks).vortex/tooling/tests/Unit/NotifyWebhookTest.php(1 hunks).vortex/tooling/tests/Unit/OverrideTest.php(1 hunks).vortex/tooling/tests/Unit/RequestTest.php(1 hunks).vortex/tooling/tests/Unit/TokenTest.php(1 hunks).vortex/tooling/tests/Unit/UnitTestCase.php(1 hunks)
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-09-13T04:14:41.765Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 2011
File: .vortex/tests/phpunit/Traits/Steps/StepAhoyTrait.php:20-23
Timestamp: 2025-09-13T04:14:41.765Z
Learning: In .vortex/tests/phpunit PHPUnit tests using cmd/cmdFail helpers, patterns in the expected output arrays support '*' markers for positive assertions (text must be present) and '!' markers for negative assertions (text must NOT be present). The '*' markers are NOT literal text matches but rather indicate expected presence.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Exceptions/QuitSuccessException.php.vortex/tooling/tests/Fixtures/test-passthru-multiple.vortex/tooling/tests/Self/MockQuitSelfTest.php.vortex/tooling/tests/Fixtures/check-exit/with-exit-in-comments.php.vortex/tooling/tests/Exceptions/QuitErrorException.php.vortex/tooling/tests/Unit/CommandExistsTest.php.vortex/tooling/tests/Fixtures/check-exit/with-exit.php.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php.vortex/tooling/tests/Unit/TokenTest.php.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-request-multiple.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Unit/GetenvTest.php.vortex/tooling/tests/Unit/FormatterTest.php.vortex/tooling/CLAUDE.md.vortex/tooling/tests/Unit/NotifySlackTest.php.vortex/tooling/tests/Fixtures/test-request-get-passing.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Unit/NotifyJiraTest.php.vortex/tooling/tests/Unit/DotenvTest.php.vortex/tooling/tests/Unit/NotifyRouterTest.php.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/tests/Fixtures/test-request-post-passing.vortex/CLAUDE.md.vortex/tooling/phpunit.xml.vortex/tooling/tests/Self/MockMailSelfTest.php.vortex/tooling/tests/Unit/UnitTestCase.php.vortex/tooling/tests/Self/CheckNoExitSelfTest.php.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/tooling/tests/Self/MockPassthruSelfTest.php.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Fixtures/check-exit/with-quit.php.vortex/tooling/tests/Fixtures/test-override-passing.vortex/tooling/tests/Unit/NotifyEmailTest.php
📚 Learning: 2025-06-01T08:09:18.993Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/hosting_acquia/docs/deployment.md:6-8
Timestamp: 2025-06-01T08:09:18.993Z
Learning: Do not review files in test fixture directories (e.g., files under paths containing `/tests/Fixtures/` or similar test fixture patterns). These files are used for testing purposes and should remain as-is.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Self/CheckNoExitSelfTest.php
📚 Learning: 2025-06-01T08:08:59.071Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1693
File: .vortex/installer/tests/Fixtures/install/deploy_type_none_gha/.github/workflows/build-test-deploy.yml:1-4
Timestamp: 2025-06-01T08:08:59.071Z
Learning: Test fixture files (typically located in directories containing "fixture" or "test") should not be reviewed for code quality issues as they may intentionally contain incomplete, invalid, or malformed data for testing purposes.
Applied to files:
.vortex/tooling/phpcs.xml.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Fixtures/test-request-post-failing.vortex/tooling/tests/Fixtures/test-quit-failing.vortex/tooling/tests/Fixtures/check-exit/with-quit.php.vortex/tooling/tests/Fixtures/test-override-passing.vortex/tooling/tests/Unit/NotifyEmailTest.php
📚 Learning: 2025-05-29T12:15:32.188Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 0
File: :0-0
Timestamp: 2025-05-29T12:15:32.188Z
Learning: Do not review files in `.vortex/installer/tests/Fixtures/install` directory as they are test fixtures.
Applied to files:
.vortex/tooling/.gitignore.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Unit/OverrideTest.php.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-override-passing
📚 Learning: 2025-08-08T12:02:24.652Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1896
File: .vortex/tests/bats/unit/download-db-lagoon.bats:24-25
Timestamp: 2025-08-08T12:02:24.652Z
Learning: In .vortex/tests/bats/unit Bats tests using ../_helper.bash (run_steps), prefixing a STEPS entry with "- " denotes a negative assertion (the substring must NOT appear in output). Unprefixed entries are positive assertions. Example: "- Database dump refresh requested. Will create a new dump." asserts absence; "Database dump refresh requested. Will create a new dump." asserts presence.
Applied to files:
.vortex/tooling/tests/Fixtures/test-request-get-failing.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/tooling/tests/Self/MockRequestSelfTest.php.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-passthru-passing.vortex/tooling/tests/Fixtures/test-quit-failing
📚 Learning: 2025-06-02T00:41:36.321Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1697
File: .vortex/installer/tests/Fixtures/install/provision_database_lagoon/docs/onboarding.md:16-17
Timestamp: 2025-06-02T00:41:36.321Z
Learning: DO NOT EVER REVIEW FILES IN .vortex/installer/tests/Fixtures/ directory - these are test fixtures and should not be reviewed under any circumstances.
Applied to files:
.vortex/tooling/tests/Fixtures/test-passthru-failing.vortex/tooling/tests/Fixtures/test-override-failing.vortex/tooling/tests/Fixtures/test-quit-passing.vortex/CLAUDE.md.vortex/tooling/tests/Fixtures/test-override-passing
📚 Learning: 2025-07-23T01:16:30.963Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1816
File: .docker/cli.dockerfile:72-76
Timestamp: 2025-07-23T01:16:30.963Z
Learning: In the Vortex project, unauthenticated Composer installs should be allowed, so GitHub token secrets should not be marked as `required=true` in Docker build mounts. The conditional check `if [ -s /run/secrets/github_token ]` is the preferred approach to allow builds to proceed without a token when needed.
Applied to files:
.github/workflows/vortex-test-tooling.yml.vortex/tooling/tests/Unit/NotifyGithubTest.php
📚 Learning: 2025-11-24T21:29:10.896Z
Learnt from: CR
Repo: drevops/vortex PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:29:10.896Z
Learning: Applies to tests/behat/features/**/*.feature : Always prefix test content with [TEST] to distinguish test data from real content
Applied to files:
.vortex/tooling/tests/Unit/FormatterTest.php
📚 Learning: 2025-06-03T03:15:29.849Z
Learnt from: AlexSkrypnyk
Repo: drevops/vortex PR: 1714
File: scripts/vortex/doctor.sh:69-70
Timestamp: 2025-06-03T03:15:29.849Z
Learning: In the Vortex project, logging functions (task(), info(), note(), pass(), fail(), warn()) are intentionally duplicated across all script files to make each script self-contained and independent, rather than sharing them through a common library. This design choice prioritizes script independence over reducing code duplication.
Applied to files:
.vortex/CLAUDE.md
📚 Learning: 2025-11-24T21:29:10.896Z
Learnt from: CR
Repo: drevops/vortex PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:29:10.896Z
Learning: Use 'ahoy build' for initial site setup, 'ahoy up/down' for environment management, and 'ahoy provision' to re-provision with fresh database
Applied to files:
.vortex/CLAUDE.md
📚 Learning: 2025-11-24T21:29:10.896Z
Learnt from: CR
Repo: drevops/vortex PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:29:10.896Z
Learning: Use 'ahoy' command prefix for all local development commands instead of running commands directly
Applied to files:
.vortex/CLAUDE.md
📚 Learning: 2025-11-24T21:29:10.896Z
Learnt from: CR
Repo: drevops/vortex PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-24T21:29:10.896Z
Learning: Applies to tests/behat/features/**/*.feature : Use descriptive step definitions and discover available steps via 'ahoy test-bdd -- --definitions=l'
Applied to files:
.vortex/CLAUDE.md
🧬 Code graph analysis (22)
.vortex/tooling/tests/Fixtures/check-exit/with-exit-in-comments.php (1)
.vortex/tooling/src/helpers.php (1)
quit(481-483)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
.vortex/tooling/tests/Exceptions/QuitSuccessException.php (2)
__construct(22-31)getOutput(39-41)
.vortex/tooling/tests/Unit/CommandExistsTest.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
command_exists(283-288)fail(265-268).vortex/tooling/tests/Traits/MockTrait.php (1)
mockQuit(279-298)
.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php (1)
.vortex/tooling/tests/Traits/MockTrait.php (3)
mockPassthruAssertAllMocksConsumed(263-265)mockMailAssertAllMocksConsumed(409-411)mockRequestAssertAllMocksConsumed(666-668)
.vortex/tooling/playground/request.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(346-352)request_post(374-381)request(403-460)
.vortex/tooling/tests/Unit/TokenTest.php (1)
.vortex/tooling/src/helpers.php (1)
replace_tokens(301-317)
.vortex/tooling/tests/Unit/OverrideTest.php (1)
.vortex/tooling/src/helpers.php (1)
execute_override(44-57)
.vortex/tooling/tests/Unit/FormatterTest.php (2)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/src/helpers.php (2)
fail(265-268)term_supports_color(273-275)
.vortex/tooling/tests/Unit/NotifySlackTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequestPost(644-658)
.vortex/tooling/tests/Unit/NotifyWebhookTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (1)
mockRequest(581-597)
.vortex/tooling/tests/Unit/NotifyJiraTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (4)
UnitTestCase(16-122)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (3)
mockRequestGet(613-626)mockRequestPost(644-658)mockRequest(581-597)
.vortex/tooling/tests/Unit/DotenvTest.php (1)
.vortex/tooling/src/helpers.php (1)
load_dotenv(65-99)
.vortex/tooling/tests/Unit/NotifyRouterTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScriptEarlyPass(94-106)runScriptError(108-120)runScript(38-92).vortex/tooling/tests/Traits/MockTrait.php (1)
mockPassthru(253-255)
.vortex/tooling/tests/Unit/RequestTest.php (1)
.vortex/tooling/src/helpers.php (3)
request_get(346-352)request_post(374-381)request(403-460)
.vortex/tooling/playground/formatters.php (1)
.vortex/tooling/src/helpers.php (1)
fail_no_exit(251-255)
.vortex/tooling/tests/Self/MockMailSelfTest.php (2)
.vortex/tooling/tests/Self/MockInfrastructureSelfTest.php (1)
CoversClass(15-27).vortex/tooling/tests/Traits/MockTrait.php (3)
mockMail(399-401)mockMailMultiple(316-389)mockMailAssertAllMocksConsumed(409-411)
.vortex/tooling/tests/Self/CheckNoExitSelfTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (2)
UnitTestCase(16-122)setUp(21-28).vortex/tooling/tests/Unit/GetenvTest.php (1)
setUp(20-23)
.vortex/tooling/tests/Unit/NotifyGithubTest.php (2)
.vortex/tooling/tests/Unit/UnitTestCase.php (3)
UnitTestCase(16-122)runScript(38-92)runScriptError(108-120).vortex/tooling/tests/Traits/MockTrait.php (2)
mockRequestPost(644-658)mockRequestGet(613-626)
.vortex/tooling/tests/Fixtures/check-exit/with-quit.php (2)
.vortex/tooling/tests/Fixtures/check-exit/with-exit.php (2)
some_condition(22-24)another_condition(26-28).vortex/tooling/src/helpers.php (1)
quit(481-483)
.vortex/tooling/tests/Traits/MockTrait.php (3)
.vortex/tooling/tests/Exceptions/QuitErrorException.php (1)
QuitErrorException(12-43).vortex/tooling/tests/Exceptions/QuitSuccessException.php (1)
QuitSuccessException(12-43).vortex/tooling/src/helpers.php (2)
fail(265-268)quit(481-483)
.vortex/tooling/tests/Unit/NotifyEmailTest.php (1)
.vortex/tooling/tests/Unit/UnitTestCase.php (5)
UnitTestCase(16-122)setUp(21-28)runScript(38-92)runScriptEarlyPass(94-106)runScriptError(108-120)
.vortex/tooling/src/helpers.php (1)
.vortex/installer/src/Utils/Tui.php (3)
line(57-59)output(38-43)error(53-55)
🪛 dotenv-linter (4.0.0)
.vortex/tooling/playground/.env
[warning] 25-25: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 29-29: [UnorderedKey] The PLAYGROUND_FLOAT key should go before the PLAYGROUND_NUMBER key
(UnorderedKey)
[warning] 33-33: [UnorderedKey] The PLAYGROUND_BOOL_FALSE key should go before the PLAYGROUND_BOOL_TRUE key
(UnorderedKey)
🪛 Gitleaks (8.29.1)
.vortex/tooling/tests/Unit/NotifyGithubTest.php
[high] 24-24: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 LanguageTool
.vortex/tooling/CLAUDE.md
[grammar] ~230-~230: Ensure spelling is correct
Context: ...ssertAllMocksConsumed(): void #### Passthru Mocking **Response Structure**:php...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🪛 markdownlint-cli2 (0.18.1)
.vortex/tooling/scripts_conversion_plan.md
42-42: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
83-83: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
97-97: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
102-102: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
113-113: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
122-122: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
144-144: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
145-145: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
147-147: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
150-150: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
151-151: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
153-153: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
156-156: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
157-157: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
160-160: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
163-163: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
164-164: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
178-178: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
179-179: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
184-184: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
185-185: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
192-192: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
202-202: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
203-203: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
239-239: Spaces inside code span elements
(MD038, no-space-in-code)
402-402: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
409-409: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
410-410: Ordered list item prefix
Expected: 1; Actual: 6; Style: 1/2/3
(MD029, ol-prefix)
411-411: Ordered list item prefix
Expected: 2; Actual: 7; Style: 1/2/3
(MD029, ol-prefix)
412-412: Ordered list item prefix
Expected: 3; Actual: 8; Style: 1/2/3
(MD029, ol-prefix)
413-413: Ordered list item prefix
Expected: 4; Actual: 9; Style: 1/2/3
(MD029, ol-prefix)
414-414: Ordered list item prefix
Expected: 5; Actual: 10; Style: 1/2/3
(MD029, ol-prefix)
415-415: Ordered list item prefix
Expected: 6; Actual: 11; Style: 1/2/3
(MD029, ol-prefix)
417-417: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
418-418: Ordered list item prefix
Expected: 1; Actual: 12; Style: 1/2/3
(MD029, ol-prefix)
419-419: Ordered list item prefix
Expected: 2; Actual: 13; Style: 1/2/3
(MD029, ol-prefix)
420-420: Ordered list item prefix
Expected: 3; Actual: 14; Style: 1/2/3
(MD029, ol-prefix)
421-421: Ordered list item prefix
Expected: 4; Actual: 15; Style: 1/2/3
(MD029, ol-prefix)
422-422: Ordered list item prefix
Expected: 5; Actual: 16; Style: 1/2/3
(MD029, ol-prefix)
452-452: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
453-453: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
461-461: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
462-462: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
470-470: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
471-471: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
479-479: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
480-480: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
492-492: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
493-493: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
505-505: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
506-506: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
514-514: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
515-515: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
526-526: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
527-527: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
539-539: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
548-548: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
557-557: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
568-568: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
632-632: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
644-644: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
649-649: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
658-658: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
666-666: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
.vortex/tooling/CLAUDE.md
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
83-83: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
129-129: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
160-160: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
218-218: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
233-233: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
243-243: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
261-261: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
306-306: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
400-400: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
407-407: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
412-412: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
480-480: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
512-512: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
527-527: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
528-528: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
537-537: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
538-538: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
545-545: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
573-573: Ordered list item prefix
Expected: 1; Actual: 2; Style: 1/1/1
(MD029, ol-prefix)
574-574: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
587-587: Ordered list item prefix
Expected: 1; Actual: 3; Style: 1/1/1
(MD029, ol-prefix)
592-592: Ordered list item prefix
Expected: 1; Actual: 4; Style: 1/1/1
(MD029, ol-prefix)
736-736: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
817-817: Bare URL used
(MD034, no-bare-urls)
818-818: Bare URL used
(MD034, no-bare-urls)
819-819: Bare URL used
(MD034, no-bare-urls)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (0)
- GitHub Check: build (1)
- GitHub Check: vortex-test-workflow (3)
- GitHub Check: vortex-test-workflow (4)
- GitHub Check: vortex-test-common
- GitHub Check: vortex-test-workflow (1)
- GitHub Check: vortex-test-workflow (0)
- GitHub Check: vortex-test-workflow (2)
- GitHub Check: vortex-test-installer (8.4)
- GitHub Check: vortex-test-installer (8.3)
- GitHub Check: vortex-test-docs
| ## .vortex Directory Structure | ||
|
|
||
| The `.vortex/` directory contains **three distinct subsystems**, each with its own purpose and technology stack: | ||
| The `.vortex/` directory contains **four distinct subsystems**, each with its own purpose and technology stack: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# Search for any remaining references to old subsystem/testing counts that should be updated
echo "=== Checking for 'three distinct' patterns ==="
rg -n 'three\s+(distinct|independent|subsystem)' .vortex/CLAUDE.md
echo -e "\n=== Checking for 'four independent testing' patterns ==="
rg -n 'four\s+independent\s+testing' .vortex/CLAUDE.md
echo -e "\n=== Checking for section numbering gaps ==="
rg -n '^###\s+[0-9]' .vortex/CLAUDE.md | tail -20Repository: drevops/vortex
<shell_result>
Repository: drevops/vortex
Exit code: 0
stdout:
=== Checking for 'three distinct' patterns ===
=== Checking for 'four independent testing' patterns ===
=== Checking for section numbering gaps ===
32:### 1. .vortex/docs/ - Documentation Website
61:### 2. .vortex/installer/ - Template Installer
92:### 3. .vortex/tooling/ - Notification & Helper Scripts
107:### 4. .vortex/tests/ - Template Testing Harness
143:### 1. Documentation Tests (.vortex/docs/)
162:### 2. Installer Tests (.vortex/installer/)
181:### 3. Tooling Tests (.vortex/tooling/)
185:### 4. Template Tests (.vortex/tests/)
203:### 5. Manual Integration Tests (.vortex/tests/manual/)
272:### 1. Documentation Tests (.vortex/docs/)
297:### 2. Installer Tests (.vortex/installer/)
322:### 3. Template Tests (.vortex/tests/)
351:### 4. Manual Integration Tests (.vortex/tests/manual/)
816:### 1. Documentation System (.vortex/docs/)
831:### 2. Installer System (.vortex/installer/)
848:### 3. Template Testing System (.vortex/tests/)
879:### 1. Documentation Issues (.vortex/docs/)
896:### 2. Installer Issues (.vortex/installer/)
920:### 3. Template Testing Issues (.vortex/tests/)
940:### 4. Manual Integration Test Issues (.vortex/tests/manual/)
</shell_result>
Script executed:
#!/bin/bash
# Read sections around the flagged lines to verify count references
echo "=== Line 30 context (subsystems) ==="
sed -n '28,32p' .vortex/CLAUDE.md
echo -e "\n=== Line 141 context ==="
sed -n '139,143p' .vortex/CLAUDE.md
echo -e "\n=== Line 569 context ==="
sed -n '567,571p' .vortex/CLAUDE.md
echo -e "\n=== Line 575 context ==="
sed -n '573,577p' .vortex/CLAUDE.md
echo -e "\n=== Line 584 context ==="
sed -n '582,586p' .vortex/CLAUDE.md
echo -e "\n=== Line 591 context ==="
sed -n '589,593p' .vortex/CLAUDE.md
echo -e "\n=== Line 1607 context ==="
sed -n '1605,1609p' .vortex/CLAUDE.mdRepository: drevops/vortex
<shell_result>
Repository: drevops/vortex
Exit code: 0
stdout:
=== Line 30 context (subsystems) ===
## .vortex Directory Structure
The `.vortex/` directory contains **four distinct subsystems**, each with its own purpose and technology stack:
### 1. .vortex/docs/ - Documentation Website
=== Line 141 context ===
## Testing Architecture Overview
Vortex uses **five independent testing systems**, each serving different parts of the codebase:
### 1. Documentation Tests (.vortex/docs/)
=== Line 569 context ===
1. **Documentation** (`.vortex/docs/`): Requires Node.js/Yarn
2. **Installer** (`.vortex/installer/`): Requires PHP/Composer
3. **Tooling** (`.vortex/tooling/`): Requires PHP/Composer
4. **Template** (`.vortex/tests/`): Requires PHP/Composer + Node.js + BATS
=== Line 575 context ===
```bash
ahoy install # Installs dependencies for all four systems
```
=== Line 584 context ===
```bash
# From .vortex/ root
ahoy install # Install all dependencies (docs, installer, tooling, template)
ahoy lint # Code linting across all systems
ahoy test # Run all template tests
=== Line 591 context ===
cd docs && yarn test # Documentation tests only
cd installer && composer test # Installer tests only
cd tooling && ./vendor/bin/phpunit # Tooling tests only
cd tests && ./vendor/bin/phpunit # Template PHPUnit tests only
ahoy test-bats -- tests/bats/ # Template BATS tests only
=== Line 1607 context ===
- Each system can be modified independently
- Changes to template (outside `.vortex/`) may require updates across all four systems
- Always run system-specific tests after making changes
- Consider impact on user workflows when modifying any system
</shell_result>
Script executed:
#!/bin/bash
# Read the testing systems section to verify all 5 are listed
echo "=== Testing Architecture Overview section (lines 141-210) ==="
sed -n '141,210p' .vortex/CLAUDE.md
echo -e "\n=== Search for any remaining count references ==="
rg -n '(three|four|five|six)\s+' .vortex/CLAUDE.md | head -30Repository: drevops/vortex
<shell_result>
Repository: drevops/vortex
Exit code: 0
stdout:
=== Testing Architecture Overview section (lines 141-210) ===
Vortex uses **five independent testing systems**, each serving different parts of the codebase:
### 1. Documentation Tests (.vortex/docs/)
**Scope**: Testing the documentation website components and content
**Technology**: Jest + React Testing Library + cspell
**What it Tests**:
- React component functionality and interactions
- MDX content rendering and navigation
- American English spelling consistency
- Documentation build processes
**Test Types**:
- **Unit tests**: Component behavior (`tests/unit/`)
- **Spellcheck**: Content validation (`cspell.json`)
- **Coverage reporting**: Multiple formats (text, lcov, HTML, Cobertura)
### 2. Installer Tests (.vortex/installer/)
**Scope**: Testing the template installation and customization logic
**Technology**: PHPUnit + Fixture System
**What it Tests**:
- User prompt handling and validation
- Template file modifications and token replacement
- Installation scenario outcomes
- Baseline vs customized template differences
**Test Types**:
- **Functional tests**: Complete installation scenarios
- **Handler tests**: Individual prompt and modification logic
- **Fixture tests**: Expected vs actual template output
### 3. Tooling Tests (.vortex/tooling/)
> **→ See `.vortex/tooling/CLAUDE.md`** for all tooling test documentation
### 4. Template Tests (.vortex/tests/)
**Scope**: Testing the actual Drupal template functionality
**Technology**: PHPUnit + BATS
**What it Tests**:
- Complete Drupal project workflows (build, provision, deploy)
- Individual shell script functionality
- Docker container interactions
- Real-world usage scenarios
**Test Types**:
- **PHPUnit Functional**: End-to-end workflow testing
- **BATS Unit**: Individual shell script testing with mocking
### 5. Manual Integration Tests (.vortex/tests/manual/)
**Scope**: Manual verification of notification integrations with real external services
**Technology**: Bash scripts + Real service APIs (Slack, JIRA, etc.)
**What it Tests**:
=== Search for any remaining count references ===
30:The `.vortex/` directory contains **four distinct subsystems**, each with its own purpose and technology stack:
141:Vortex uses **five independent testing systems**, each serving different parts of the codebase:
575:ahoy install # Installs dependencies for all four systems
761:**Critical Understanding**: The `.vortex/` directory contains three **completely independent subsystems**:
1607:- Changes to template (outside `.vortex/`) may require updates across all four systems
1613:*This knowledge base should be updated whenever significant changes are made to any of the four Vortex subsystems or their maintenance procedures.*
</shell_result>
Script executed:
#!/bin/bash
# Read context around the problematic line 761
echo "=== Line 761 context (inconsistency found) ==="
sed -n '759,765p' .vortex/CLAUDE.md
# Double-check for any other "three" count references
echo -e "\n=== All count references in document ==="
rg -n '(three|four|five)\s+(distinct|independent|subsystem|system|testing)' .vortex/CLAUDE.mdRepository: drevops/vortex
<shell_result>
Repository: drevops/vortex
Exit code: 0
stdout:
=== Line 761 context (inconsistency found) ===
### Test Harness (.vortex/) - Three Separate Systems
**Critical Understanding**: The `.vortex/` directory contains three **completely independent subsystems**:
1. **`.vortex/docs/`** - Docusaurus website (Node.js/React)
2. **`.vortex/installer/`** - PHP installer application (Symfony Console)
3. **`.vortex/tests/`** - Template testing harness (PHPUnit + BATS)
=== All count references in document ===
30:The `.vortex/` directory contains **four distinct subsystems**, each with its own purpose and technology stack:
141:Vortex uses **five independent testing systems**, each serving different parts of the codebase:
575:ahoy install # Installs dependencies for all four systems
1607:- Changes to template (outside `.vortex/`) may require updates across all four systems
</shell_result>
Line 761 contains an outdated count that contradicts the document's established architecture. The section header and description reference "three completely independent subsystems" but omit .vortex/tooling/, which is explicitly included as the third subsystem in the main architecture overview (line 30). The section should list all four subsystems: docs, installer, tooling, and tests—and update the count from three to four.
The remaining references (lines 30, 141, 575, 1607) are consistent: four subsystems and five testing systems.
🤖 Prompt for AI Agents
.vortex/CLAUDE.md around line 761: the header and sentence on this line
incorrectly state "three completely independent subsystems" and omit
`.vortex/tooling/`; update the count to "four" and revise the list to explicitly
name all four subsystems as "docs, installer, tooling, and tests" so the
sentence matches the main architecture overview and other references.
| # Multi-word value without quotes | ||
| PLAYGROUND_MULTIWORD=this has spaces and should work | ||
|
|
||
| # Numbers | ||
| PLAYGROUND_NUMBER=12345 | ||
| PLAYGROUND_FLOAT=123.45 | ||
|
|
||
| # Booleans (as strings) | ||
| PLAYGROUND_BOOL_TRUE=true | ||
| PLAYGROUND_BOOL_FALSE=false | ||
|
|
||
| # URLs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Decide whether to satisfy dotenv-linter or keep intentional “gotcha” examples
PLAYGROUND_MULTIWORD=this has spaces and should work and the bool/number ordering trigger dotenv-linter warnings, but the surrounding comments explicitly demonstrate unquoted multi-word values and ordering is irrelevant for behavior. If you want a clean dotenv-linter run, quote the multi-word value and reorder the BOOL/NUMBER keys; otherwise, consider disabling those specific checks for this playground file.
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 25-25: [ValueWithoutQuotes] This value needs to be surrounded in quotes
(ValueWithoutQuotes)
[warning] 29-29: [UnorderedKey] The PLAYGROUND_FLOAT key should go before the PLAYGROUND_NUMBER key
(UnorderedKey)
[warning] 33-33: [UnorderedKey] The PLAYGROUND_BOOL_FALSE key should go before the PLAYGROUND_BOOL_TRUE key
(UnorderedKey)
🤖 Prompt for AI Agents
.vortex/tooling/playground/.env around lines 24 to 35: the dotenv-linter flags
are raised by the unquoted multi-word value and the ordering of numeric and
boolean keys; either quote the multi-word value and reorder the keys to satisfy
the linter (e.g., change PLAYGROUND_MULTIWORD=this has spaces and should work to
PLAYGROUND_MULTIWORD="this has spaces and should work" and place
PLAYGROUND_NUMBER/PLAYGROUND_FLOAT before the BOOL entries), or keep the
examples as-is and suppress the linter for this file by adding the appropriate
dotenv-linter disable directive for these lines; apply one of these fixes so the
repository linter run is intentional.
| // Test 1: Simple GET request. | ||
| echo "Test 1: GET request to httpbin.org/get\n"; | ||
| $result = request_get('https://httpbin.org/get'); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| echo " Body preview: " . substr((string) $result['body'], 0, 100) . "...\n"; | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 2: GET with headers. | ||
| echo "Test 2: GET request with custom headers\n"; | ||
| $result = request_get( | ||
| 'https://httpbin.org/headers', | ||
| ['X-Custom-Header: TestValue', 'X-Another-Header: AnotherValue'] | ||
| ); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| if ($result['ok']) { | ||
| echo " Check response body for custom headers:\n"; | ||
| echo " " . substr((string) $result['body'], 0, 200) . "...\n"; | ||
| } | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 3: POST request with JSON. | ||
| echo "Test 3: POST request with JSON body\n"; | ||
| $result = request_post( | ||
| 'https://httpbin.org/post', | ||
| '{"test": "data", "number": 123, "array": [1, 2, 3]}', | ||
| ['Content-Type: application/json'] | ||
| ); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| if ($result['ok']) { | ||
| echo " Response preview:\n"; | ||
| echo " " . substr((string) $result['body'], 0, 200) . "...\n"; | ||
| } | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 4: Testing error handling (404). | ||
| echo "Test 4: Testing error handling (404)\n"; | ||
| $result = request_get('https://httpbin.org/status/404'); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| $expected_fail = !$result['ok']; | ||
| echo " Result: " . ($expected_fail ? "✓ SUCCESS (404 correctly detected as error)" : "✗ FAILED (404 should be detected as error)") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 5: Testing 500 error. | ||
| echo "Test 5: Testing server error (500)\n"; | ||
| $result = request_get('https://httpbin.org/status/500'); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| $expected_fail = !$result['ok']; | ||
| echo " Result: " . ($expected_fail ? "✓ SUCCESS (500 correctly detected as error)" : "✗ FAILED (500 should be detected as error)") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 6: Custom method (PUT). | ||
| echo "Test 6: Custom method (PUT) using request()\n"; | ||
| $result = request( | ||
| 'https://httpbin.org/put', | ||
| [ | ||
| 'method' => 'PUT', | ||
| 'body' => '{"updated": true, "timestamp": 1234567890}', | ||
| 'headers' => ['Content-Type: application/json'], | ||
| ] | ||
| ); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 7: DELETE method. | ||
| echo "Test 7: Custom method (DELETE) using request()\n"; | ||
| $result = request( | ||
| 'https://httpbin.org/delete', | ||
| ['method' => 'DELETE'] | ||
| ); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
| echo "\n"; | ||
|
|
||
| // Test 8: Timeout handling. | ||
| echo "Test 8: Testing timeout (3 second delay with 1 second timeout)\n"; | ||
| echo " This should trigger a timeout error...\n"; | ||
| $result = request_get('https://httpbin.org/delay/3', [], 1); | ||
| if (!empty($result['error'])) { | ||
| echo " Error: " . $result['error'] . "\n"; | ||
| echo " Result: ✓ SUCCESS (Timeout correctly detected)\n"; | ||
| } | ||
| else { | ||
| echo " Result: ✗ FAILED (Timeout not detected)\n"; | ||
| } | ||
| echo "\n"; | ||
|
|
||
| // Test 9: Successful request with custom timeout. | ||
| echo "Test 9: Successful request with longer timeout\n"; | ||
| $result = request_get('https://httpbin.org/delay/1', [], 5); | ||
| echo " Status: " . $result['status'] . "\n"; | ||
| echo " OK: " . ($result['ok'] ? 'true' : 'false') . "\n"; | ||
| echo " Result: " . ($result['ok'] ? "✓ SUCCESS" : "✗ FAILED") . "\n"; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding a fallback or note about external dependency.
The demo relies on httpbin.org being available. Consider adding a brief note at the top about this dependency, or a check that the service is reachable before running all tests.
🤖 Prompt for AI Agents
In .vortex/tooling/playground/request.php around lines 19-122 the test suite
assumes httpbin.org is reachable; add either a short preflight check (e.g., a
quick GET to https://httpbin.org/get with a 1–2s timeout) and if it fails print
a clear note and skip the tests (or exit non-fatally), or add a top-of-file
comment explaining the external dependency and how to override/skip tests (for
example via an environment variable). Implement the preflight so the script
cleanly reports "httpbin.org unreachable — skipping tests" and avoids running
the long sequence when the service is down.
| // Test 8: Timeout handling. | ||
| echo "Test 8: Testing timeout (3 second delay with 1 second timeout)\n"; | ||
| echo " This should trigger a timeout error...\n"; | ||
| $result = request_get('https://httpbin.org/delay/3', [], 1); | ||
| if (!empty($result['error'])) { | ||
| echo " Error: " . $result['error'] . "\n"; | ||
| echo " Result: ✓ SUCCESS (Timeout correctly detected)\n"; | ||
| } | ||
| else { | ||
| echo " Result: ✗ FAILED (Timeout not detected)\n"; | ||
| } | ||
| echo "\n"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Timeout test result check could be more robust.
Test 8 assumes a timeout will occur and only checks for $result['error'], but doesn't verify the ok flag is false. Also, the timeout test relies on httpbin.org's /delay/3 endpoint which may occasionally behave differently.
-if (!empty($result['error'])) {
+if (!$result['ok'] && !empty($result['error'])) {
echo " Error: " . $result['error'] . "\n";
echo " Result: ✓ SUCCESS (Timeout correctly detected)\n";
}🤖 Prompt for AI Agents
.vortex/tooling/playground/request.php around lines 103 to 114: the timeout test
only checks for $result['error'] and assumes httpbin.org/delay/3 will always
trigger a timeout; update the check to assert that $result['ok'] is false in
addition to $result['error'], and make the test more resilient by either using a
deterministic local/mock endpoint or by validating the error text contains a
timeout indication (e.g. "timeout" or cURL timeout code) so intermittent remote
behavior won't produce false positives; change the messages to reflect both
conditions (error present AND ok === false) to mark the test as success,
otherwise mark as failure.
| $exit_code = 0; | ||
| passthru(sprintf('"%s"', $script), $exit_code); | ||
|
|
||
| if ($exit_code !== 0) { | ||
| fail('Notification to %s failed with exit code %s', $channel, $exit_code); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Script path quoting may break if path contains special characters.
The sprintf('"%s"', $script) approach could fail if $script contains double quotes. While unlikely for controlled paths, consider using escapeshellarg() for robustness:
- passthru(sprintf('"%s"', $script), $exit_code);
+ passthru(escapeshellarg($script), $exit_code);🤖 Prompt for AI Agents
In .vortex/tooling/src/notify around lines 72 to 77, the current sprintf('"%s"',
$script) can break when $script contains embedded double quotes or other
shell-sensitive characters; replace the manual quoting with
escapeshellarg($script) when building the command passed to passthru (i.e., call
passthru(escapeshellarg($script), $exit_code)) so the script path is safely
escaped for the shell and keeps the existing exit-code handling and fail()
behavior unchanged.
| protected function mockRequestGet( | ||
| string $url, | ||
| array $headers = [], | ||
| int $timeout = 10, | ||
| ?array $response = NULL, | ||
| string $namespace = 'DrevOps\\VortexTooling', | ||
| ): void { | ||
| $this->mockRequestMultiple([[ | ||
| 'url' => $url, | ||
| 'method' => 'GET', | ||
| 'response' => $response ?? [], | ||
| ], | ||
| ], $namespace); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
mockRequestGet() ignores $headers and $timeout parameters
Similar to mockRequestPost(), the $headers and $timeout parameters are accepted but not forwarded to mockRequestMultiple(). While less critical than body validation, this could mislead developers into thinking these parameters affect mock matching.
Either forward the parameters or remove them from the signature to avoid confusion.
🤖 Prompt for AI Agents
.vortex/tooling/tests/Traits/MockTrait.php around lines 613 to 626:
mockRequestGet currently accepts $headers and $timeout but doesn't pass them to
mockRequestMultiple; update the call to mockRequestMultiple so the single
request entry includes 'headers' => $headers and 'timeout' => $timeout (matching
the structure used by mockRequestPost), or if headers/timeout are intentionally
unused, remove them from the method signature and any callers; ensure the
request array keys exactly match what mockRequestMultiple expects so mock
matching works as intended.
| protected function mockRequestPost( | ||
| string $url, | ||
| $body = NULL, | ||
| array $headers = [], | ||
| int $timeout = 10, | ||
| ?array $response = NULL, | ||
| string $namespace = 'DrevOps\\VortexTooling', | ||
| ): void { | ||
| $this->mockRequestMultiple([[ | ||
| 'url' => $url, | ||
| 'method' => 'POST', | ||
| 'response' => $response ?? [], | ||
| ], | ||
| ], $namespace); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mockRequestPost() ignores $body, $headers, and $timeout parameters
The method signature accepts $body, $headers, and $timeout parameters to mirror the real request_post() function, but none of these are forwarded to mockRequestMultiple(). Only url, method, and response are used.
This causes issues in tests (like NotifyJiraTest) where callbacks are passed as $body expecting validation, but they're silently ignored. The test appears to pass while the payload assertions never run.
Consider either:
- Forward
$bodyas a matcher/validator to the mock infrastructure:
protected function mockRequestPost(
string $url,
$body = NULL,
array $headers = [],
int $timeout = 10,
?array $response = NULL,
string $namespace = 'DrevOps\\VortexTooling',
): void {
$this->mockRequestMultiple([[
'url' => $url,
'method' => 'POST',
+ 'body' => $body,
+ 'headers' => $headers,
'response' => $response ?? [],
],
], $namespace);
}- Remove unused parameters to avoid misleading API
- Document that these parameters are intentionally ignored
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
.vortex/tooling/tests/Traits/MockTrait.php around lines 644 to 658:
mockRequestPost currently accepts $body, $headers, and $timeout but does not
forward them to mockRequestMultiple, so tests that pass callbacks or expect
header/timeout checks are ignored; update the inner array passed to
mockRequestMultiple to include 'body' => $body, 'headers' => $headers, and
'timeout' => $timeout (ensuring null/defaults are preserved) so the mock
infrastructure can run validators or matchers, and keep 'response' => $response
?? []; alternatively, if you prefer the simpler option, remove the unused
parameters from the signature and update callers — but the recommended fix is to
forward these parameters so callbacks/validators are executed by the mock.
| public function testCommandExistsAvailable(): void { | ||
| $this->expectNotToPerformAssertions(); | ||
|
|
||
| \DrevOps\VortexTooling\command_exists('php'); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding a positive assertion for the available command test.
Using expectNotToPerformAssertions() only verifies that no exception is thrown but doesn't confirm the function actually validated the command. While this may be intentional (since command_exists is void), adding a comment explaining the intent would improve clarity.
🤖 Prompt for AI Agents
In .vortex/tooling/tests/Unit/CommandExistsTest.php around lines 26-30 the test
uses expectNotToPerformAssertions() which only ensures no exception is thrown
and doesn't clearly document intent; either replace the no-assertion placeholder
with a minimal positive assertion (for example incrementing the assertion count)
or add a concise comment above the call explaining that command_exists is void
and the test only verifies it does not throw, so future readers understand this
is intentional.
| #[DataProvider('dataProviderLoadDotenv')] | ||
| public function testLoadDotenv(array $env_content, array $expected): void { | ||
| $env_file = self::$tmp . '/.env.test'; | ||
| $lines = []; | ||
| foreach ($env_content as $key => $value) { | ||
| $lines[] = sprintf('%s=%s', $key, $value); | ||
| } | ||
| file_put_contents($env_file, implode("\n", $lines)); | ||
|
|
||
| \DrevOps\VortexTooling\load_dotenv([$env_file]); | ||
|
|
||
| foreach ($expected as $key => $expected_value) { | ||
| $this->assertEquals($expected_value, getenv($key)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Environment variables are not cleaned up after tests.
The test sets environment variables via load_dotenv() which calls putenv(), but there's no tearDown to remove them. This can cause test pollution if these variable names conflict with other tests or if test order matters.
Consider adding cleanup in tearDown or tracking and removing set variables:
protected function tearDown(): void {
// Clean up any test environment variables
foreach (['TEST_VAR_1', 'TEST_VAR_2', 'TEST_QUOTED_DOUBLE', 'TEST_QUOTED_SINGLE',
'TEST_EQUALS', 'TEST_SPACES', 'TEST_VAR', 'TEST_VAR2'] as $var) {
putenv($var);
}
parent::tearDown();
}🤖 Prompt for AI Agents
In .vortex/tooling/tests/Unit/DotenvTest.php around lines 26-40, the test sets
environment variables via load_dotenv() but does not remove them afterwards,
risking test pollution; add a protected tearDown(): void method that iterates
the exact keys set by the data provider and unsets each (use putenv("$key=") or
equivalent to remove the variable) and then calls parent::tearDown() so the
environment is cleaned between tests.
| public function testSuccessfulNotificationSingleRecipient(): void { | ||
| $this->mockMail([ | ||
| 'to' => '[email protected]', | ||
| 'subject' => 'test-project deployment notification of main', | ||
| 'message' => "## This is an automated message ##\nSite test-project main has been deployed at " . date('d/m/Y H:i:s T') . " and is available at https://example.com.\nLogin at: https://example.com/login", | ||
| 'result' => TRUE, | ||
| ]); | ||
|
|
||
| $output = $this->runScript('src/notify-email'); | ||
|
|
||
| $this->assertStringContainsString('Started email notification', $output); | ||
| $this->assertStringContainsString('Project : test-project', $output); | ||
| $this->assertStringContainsString('From : [email protected]', $output); | ||
| $this->assertStringContainsString('Recipients : [email protected]', $output); | ||
| $this->assertStringContainsString('Sending email notification', $output); | ||
| $this->assertStringContainsString('Email notification sent successfully to 1 recipient(s)', $output); | ||
| $this->assertStringContainsString('Finished email notification', $output); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential timestamp flakiness in mail message assertions
The expected message includes date('d/m/Y H:i:s T') which captures the timestamp when the mock is set up. If the script execution crosses a second boundary, the timestamp in the actual message won't match, causing a spurious test failure.
Consider either:
- Using a more lenient assertion that ignores the timestamp portion
- Mocking the time function
- Using a regex pattern to match the message structure
This applies to multiple tests using the same pattern (lines 38, 59, 66, 83, 151, 168, 184, 214, etc.).
🤖 Prompt for AI Agents
.vortex/tooling/tests/Unit/NotifyEmailTest.php around lines 34 to 51: the test
builds an exact timestamp with date('d/m/Y H:i:s T') into the mocked mail
message which can drift by a second between setup and script execution causing
flaky failures; update the test to avoid asserting the exact timestamp — either
mock the time function so both mock and script use the same fixed time, or
change the assertion to use a regex or substring match that verifies the message
structure and static parts but ignores or loosely matches the timestamp portion
(e.g. assert that the message contains the fixed prefix/suffix and a
timestamp-like pattern) and apply the same change to the other tests listed.
Related #1192
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.