@@ -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
98117quit(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
278487mockRequest(
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
286495mockRequestGet(
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
295504mockRequestPost(
296- string $url ,
505+ string $deploy_webhook_url ,
297506 $body = NULL,
298507 array $headers = [],
299508 int $timeout = 10,
0 commit comments