Skip to content

Commit 350145a

Browse files
committed
[#1192] Added deployment scripts to tooling.
1 parent fa3abbf commit 350145a

39 files changed

+5139
-339
lines changed

.vortex/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,12 +1580,12 @@ $this->cmd('ahoy export-db', '! Containers are not running.', arg: $args);
15801580
15811581
```php
15821582
// ✅ GOOD - Single-line code
1583-
throw new \RuntimeException(sprintf('Failed to download archive from: %s - %s', $url, $e->getMessage()));
1583+
throw new \RuntimeException(sprintf('Failed to download archive from: %s - %s', $deploy_webhook_url, $e->getMessage()));
15841584
15851585
// ❌ AVOID - Multi-line code when not necessary
15861586
throw new \RuntimeException(sprintf(
15871587
'Failed to download archive from: %s - %s',
1588-
$url,
1588+
$deploy_webhook_url,
15891589
$e->getMessage()
15901590
));
15911591

.vortex/tooling/CLAUDE.md

Lines changed: 217 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ This document describes the Vortex tooling package - a collection of PHP helper
3535
│ │ ├── UnitTestCase.php # Base test case
3636
│ │ ├── ExitException.php # Exit exception for testing
3737
│ │ ├── ExitMockTest.php # Exit mocking tests
38-
│ │ ├── DotenvTest.php # Environment tests
38+
│ │ ├── HelpersDotenvTest.php # Environment tests
3939
│ │ └── SelfTest.php # Self-tests for core functions
4040
│ └── Traits/
4141
│ └── MockTrait.php # Mock infrastructure (passthru, quit, request)
@@ -74,9 +74,9 @@ validate_command(string $command): void
7474
### HTTP Request Functions
7575

7676
```php
77-
request_get(string $url, array $headers = [], int $timeout = 10): array
78-
request_post(string $url, $body = NULL, array $headers = [], int $timeout = 10): array
79-
request(string $url, array $options = []): array
77+
request_get(string $deploy_webhook_url, array $headers = [], int $timeout = 10): array
78+
request_post(string $deploy_webhook_url, $body = NULL, array $headers = [], int $timeout = 10): array
79+
request(string $deploy_webhook_url, array $options = []): array
8080
```
8181

8282
**Return Format**:
@@ -90,6 +90,25 @@ request(string $url, array $options = []): array
9090
]
9191
```
9292

93+
### Environment Variable Functions
94+
95+
```php
96+
getenv_required(...$var_names): string
97+
getenv_default(...$args): string
98+
```
99+
100+
**getenv_required()** - Get value from environment variables with fallback chain:
101+
- Tries each variable name in order
102+
- Returns first non-empty value found
103+
- Calls `fail()` and `quit(1)` if no value found
104+
- Example: `getenv_required('VAR1', 'VAR2', 'VAR3')`
105+
106+
**getenv_default()** - Get value with fallback chain and default:
107+
- Last argument is the default value
108+
- Tries each variable name in order
109+
- Returns first non-empty value found, or default if none found
110+
- Example: `getenv_default('VAR1', 'VAR2', 'default_value')`
111+
93112
### Utility Functions
94113

95114
```php
@@ -98,6 +117,106 @@ is_debug(): bool
98117
quit(int $code = 0): void // Wrapper around exit() for testing
99118
```
100119

120+
## Script Structure Guidelines
121+
122+
All scripts in `src/` must follow a consistent structure for maintainability and clarity:
123+
124+
### Standard Script Structure
125+
126+
```php
127+
#!/usr/bin/env php
128+
<?php
129+
130+
/**
131+
* @file
132+
* Brief description of what the script does.
133+
*
134+
* Additional details about the script's purpose or requirements.
135+
*
136+
* IMPORTANT! This script runs outside the container on the host system.
137+
*/
138+
139+
declare(strict_types=1);
140+
141+
namespace DrevOps\VortexTooling;
142+
143+
require_once __DIR__ . '/helpers.php';
144+
145+
execute_override(basename(__FILE__));
146+
147+
// -----------------------------------------------------------------------------
148+
149+
// Variable description.
150+
//
151+
// Additional details about format, usage, or valid values.
152+
$var1 = getenv_required('PRIMARY_VAR', 'FALLBACK_VAR');
153+
154+
// Another variable description.
155+
//
156+
// Can be 'value1', 'value2', etc.
157+
$var2 = getenv_default('VAR_NAME', 'default_value');
158+
159+
// Optional variable with detailed format explanation.
160+
//
161+
// Format: key1=value1,key2=value2
162+
// Example: web=myorg/myapp,db=myorg/mydb
163+
$var3 = getenv_default('OPTIONAL_VAR', '');
164+
165+
// -----------------------------------------------------------------------------
166+
167+
info('Started operation.');
168+
169+
// Main script logic here...
170+
171+
pass('Finished operation.');
172+
```
173+
174+
### Environment Variable Best Practices
175+
176+
1. **Use VARIABLES Section**: All environment variable declarations at the top
177+
2. **Document Each Variable**: Include comment explaining purpose and format
178+
3. **Use Fallback Chains**: `getenv_required()` and `getenv_default()` with multiple variable names
179+
4. **Separate Sections**: Use comment dividers to separate VARIABLES from EXECUTION
180+
5. **Explicit Validation**: Check for explicitly empty values when needed:
181+
182+
```php
183+
// Check if variable is explicitly set to empty (different from not set).
184+
if (($check = getenv('VAR_NAME')) !== FALSE && empty(trim($check))) {
185+
fail('VAR_NAME should not be empty.');
186+
quit(1);
187+
}
188+
```
189+
190+
### Variable Documentation Format
191+
192+
Each variable should have:
193+
- **Single-line comment**: Brief description
194+
- **Multi-line comment** (optional): Format details, examples, valid values
195+
- **Variable assignment**: Using getenv_required() or getenv_default()
196+
197+
**Example**:
198+
199+
```php
200+
// Email notification recipients.
201+
//
202+
// Multiple names can be specified as a comma-separated list of email addresses
203+
// with optional names in the format "email|name".
204+
// Example: "to1@example.com|Jane Doe, to2@example.com|John Doe".
205+
$email_recipients = getenv_required('VORTEX_NOTIFY_EMAIL_RECIPIENTS');
206+
207+
// Email notification subject template.
208+
//
209+
// Available tokens:
210+
// - %project% - Project name
211+
// - %label% - Deployment label
212+
// - %timestamp% - Deployment timestamp
213+
$email_subject = getenv_default('VORTEX_NOTIFY_EMAIL_SUBJECT', '%project% deployment notification');
214+
```
215+
216+
### Real-World Example
217+
218+
See `src/notify-email` for a complete example following this structure.
219+
101220
## Testing Architecture
102221

103222
### Test Organization
@@ -118,8 +237,98 @@ The package uses **three types of tests**:
118237
- Use `$this->envUnsetPrefix('PREFIX_')` for unsetting all variables with a prefix
119238
- NEVER use `putenv()` directly - always use EnvTrait methods for automatic cleanup
120239

240+
**Data Providers - Use Them Extensively**:
241+
- **ALWAYS use data providers** to reduce test duplication when testing the same logic with different inputs
242+
- Use `#[DataProvider('dataProviderMethodName')]` attribute on test methods
243+
- Data provider method names must start with `dataProvider` prefix (e.g., `dataProviderHttpMethods`, not `providerHttpMethods`)
244+
- Data provider methods must be `public static` and return an array
245+
246+
**Advanced Data Provider Patterns**:
247+
248+
When tests need setup or additional assertions, use closures in data provider arrays:
249+
250+
```php
251+
public static function dataProviderWithSetupAndAssertions(): array {
252+
return [
253+
'scenario name' => [
254+
'input' => 'test value',
255+
'expected' => 'expected value',
256+
'before' => function(self $test): void {
257+
// Setup code executed before the test
258+
$test->envSet('SOME_VAR', 'value');
259+
$test->mockShellExec('output');
260+
},
261+
'after' => function(self $test, $result): void {
262+
// Additional assertions executed after the test
263+
$test->assertStringContainsString('expected', $result);
264+
$test->assertFileExists('/tmp/test.txt');
265+
},
266+
],
267+
];
268+
}
269+
270+
#[DataProvider('dataProviderWithSetupAndAssertions')]
271+
public function testWithSetupAndAssertions(
272+
string $input,
273+
string $expected,
274+
?\Closure $before = NULL,
275+
?\Closure $after = NULL
276+
): void {
277+
// Execute before closure if provided
278+
if ($before !== NULL) {
279+
$before($this);
280+
}
281+
282+
// Main test logic
283+
$result = someFunction($input);
284+
$this->assertEquals($expected, $result);
285+
286+
// Execute after closure if provided
287+
if ($after !== NULL) {
288+
$after($this, $result);
289+
}
290+
}
291+
```
292+
293+
**When to Use Before/After Closures**:
294+
- **Before**: When different test cases need different mock setups, environment variables, or file preparations
295+
- **After**: When different test cases need additional scenario-specific assertions beyond the main test logic
296+
- **Avoid**: Don't use closures for simple cases - keep data providers simple when possible
297+
298+
**Real-World Example from Codebase**:
299+
300+
See `tests/Self/MockShellExecSelfTest.php` for simple data provider usage:
301+
302+
```php
303+
#[DataProvider('dataProviderMockShellExec')]
304+
public function testMockShellExec(string|null|false $mock_value): void {
305+
$this->mockShellExec($mock_value);
306+
$result = shell_exec('echo "test"');
307+
308+
if ($mock_value === NULL) {
309+
$this->assertNull($result);
310+
}
311+
elseif ($mock_value === FALSE) {
312+
$this->assertFalse($result);
313+
}
314+
else {
315+
$this->assertEquals($mock_value, $result);
316+
}
317+
}
318+
319+
public static function dataProviderMockShellExec(): array {
320+
return [
321+
'string output' => ['command output'],
322+
'null output' => [NULL],
323+
'false output' => [FALSE],
324+
'empty string output' => [''],
325+
];
326+
}
327+
```
328+
329+
This replaces 4 separate test methods with a single parameterized test.
330+
121331
**Documentation**:
122-
- Data provider method names should start with `dataProvider` prefix (e.g., `dataProviderHttpMethods`, not `providerHttpMethods`)
123332
- Block comments (PHPDoc /** ... */) are ONLY allowed on test classes, NOT on methods
124333
- Do NOT add block comments to test methods, data provider methods, or helper methods
125334
- Inline comments (// ...) are acceptable for explaining logic within method bodies
@@ -276,15 +485,15 @@ The mock API mirrors the actual request function signatures for consistency and
276485
```php
277486
// Mock request() - matches request() signature + $response parameter
278487
mockRequest(
279-
string $url,
488+
string $deploy_webhook_url,
280489
array $options = [],
281490
array $response = [],
282491
string $namespace = 'DrevOps\\VortexTooling'
283492
): void
284493

285494
// Mock request_get() - matches request_get() signature + $response parameter
286495
mockRequestGet(
287-
string $url,
496+
string $deploy_webhook_url,
288497
array $headers = [],
289498
int $timeout = 10,
290499
array $response = [],
@@ -293,7 +502,7 @@ mockRequestGet(
293502

294503
// Mock request_post() - matches request_post() signature + $response parameter
295504
mockRequestPost(
296-
string $url,
505+
string $deploy_webhook_url,
297506
$body = NULL,
298507
array $headers = [],
299508
int $timeout = 10,

.vortex/tooling/composer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
"php": ">=8.2",
2121
"alexskrypnyk/file": "^0.15",
2222
"alexskrypnyk/phpunit-helpers": "^0.14.0",
23-
"dealerdirect/phpcodesniffer-composer-installer": "^1.1.2",
23+
"dealerdirect/phpcodesniffer-composer-installer": "^1.2.0",
2424
"drevops/phpcs-standard": "^0.5",
2525
"drupal/coder": "^9@alpha",
2626
"ergebnis/composer-normalize": "^2.48.2",
2727
"php-mock/php-mock-phpunit": "^2.14",
28-
"phpstan/phpstan": "^2.1.31",
29-
"phpunit/phpunit": "^12.4.2",
30-
"rector/rector": "^2.2.7",
31-
"squizlabs/php_codesniffer": "^4"
28+
"phpstan/phpstan": "^2.1.33",
29+
"phpunit/phpunit": "^12.5.3",
30+
"rector/rector": "^2.2.14",
31+
"squizlabs/php_codesniffer": "^4.0.1"
3232
},
3333
"minimum-stability": "alpha",
3434
"prefer-stable": true,

.vortex/tooling/phpunit.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
requireCoverageMetadata="false"
77
beStrictAboutCoverageMetadata="false"
88
beStrictAboutOutputDuringTests="true"
9-
failOnRisky="true"
9+
failOnSkipped="true"
1010
failOnWarning="true"
11+
stopOnError="true"
12+
stopOnFailure="true"
13+
stopOnRisky="true"
14+
stopOnWarning="true"
1115
displayDetailsOnTestsThatTriggerWarnings="true"
1216
displayDetailsOnTestsThatTriggerErrors="true"
1317
displayDetailsOnTestsThatTriggerNotices="true"

.vortex/tooling/rector.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Rector\CodingStyle\Rector\Catch_\CatchExceptionNameMatchingTypeRector;
1919
use Rector\CodingStyle\Rector\ClassMethod\NewlineBeforeNewAssignSetRector;
2020
use Rector\CodingStyle\Rector\FuncCall\CountArrayToEmptyArrayComparisonRector;
21-
use Rector\CodingStyle\Rector\FunctionLike\FunctionLikeToFirstClassCallableRector;
2221
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
2322
use Rector\Config\RectorConfig;
2423
use Rector\DeadCode\Rector\If_\RemoveAlwaysTrueIfConditionRector;
@@ -28,7 +27,6 @@
2827
use Rector\Naming\Rector\ClassMethod\RenameVariableToMatchNewTypeRector;
2928
use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector;
3029
use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector;
31-
use Rector\Php81\Rector\Array_\FirstClassCallableRector;
3230
use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector;
3331
use Rector\Privatization\Rector\MethodCall\PrivatizeLocalGetterToPropertyRector;
3432
use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
@@ -55,8 +53,6 @@
5553
CompleteDynamicPropertiesRector::class,
5654
CountArrayToEmptyArrayComparisonRector::class,
5755
DisallowedEmptyRuleFixerRector::class,
58-
FirstClassCallableRector::class,
59-
FunctionLikeToFirstClassCallableRector::class,
6056
InlineArrayReturnAssignRector::class,
6157
NewlineAfterStatementRector::class,
6258
NewlineBeforeNewAssignSetRector::class,

0 commit comments

Comments
 (0)