diff --git a/app/Console/Commands/ControlTowerPollCommand.php b/app/Console/Commands/ControlTowerPollCommand.php index 6119b46..8b585d6 100644 --- a/app/Console/Commands/ControlTowerPollCommand.php +++ b/app/Console/Commands/ControlTowerPollCommand.php @@ -67,7 +67,8 @@ public function handle(): int "METADATA:\n" . json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), ]); - $wrapperScript = (string) ($this->option('wrapper-script') ?: base_path('scripts/ops/antigravity-cli/run_205_followup_via_agy.sh')); + $defaultWrapperScript = '/home/michele/netgescon-day0-backup/scripts/ops/antigravity-cli/run_205_followup_via_agy.sh'; + $wrapperScript = (string) ($this->option('wrapper-script') ?: $defaultWrapperScript); if (! file_exists($wrapperScript)) { $this->error("❌ Script wrapper non trovato: {$wrapperScript}"); @@ -80,7 +81,15 @@ public function handle(): int 2 => ['pipe', 'w'], ]; - $process = proc_open($wrapperScript, $descriptors, $pipes, base_path()); + $env = [ + 'HOME' => getenv('HOME') ?: '/home/michele', + 'USER' => getenv('USER') ?: 'michele', + 'LOGNAME' => getenv('LOGNAME') ?: 'michele', + 'PATH' => '/home/michele/.local/bin:/usr/local/bin:/usr/bin:/bin', + 'AGY_MODE' => 'plan', + ]; + + $process = proc_open($wrapperScript, $descriptors, $pipes, base_path(''), $env); if (! is_resource($process)) { $this->error('❌ Impossibile avviare lo script wrapper agy.'); return self::FAILURE; diff --git a/tests/Feature/ControlTowerPollCommandTest.php b/tests/Feature/ControlTowerPollCommandTest.php index 8f0f650..26219d2 100644 --- a/tests/Feature/ControlTowerPollCommandTest.php +++ b/tests/Feature/ControlTowerPollCommandTest.php @@ -7,7 +7,101 @@ Http::preventStrayRequests(); }); -it('publishes report when wrapper returns NETTOWER_AGY_ULTRA_WRAPPER_OK', function () { +it('verifies process environment HOME PATH AGY_MODE, absolute path, and preserves stdout/stderr', function () { + $taskId = 'task-test-env-001'; + + Http::fake([ + 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId, 'title' => 'Test Env', 'description' => 'Desc']], 200), + 'http://192.168.0.53:4174/api/reports' => Http::response(['id' => 'rep-env-123'], 201), + 'http://192.168.0.53:4174/api/tasks/status' => Http::response(['status' => 'done'], 200), + 'http://192.168.0.53:4174/api/machines/heartbeat' => Http::response(['status' => 'online'], 200), + ]); + + $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_env_') . '.sh'; + + $scriptContent = <<<'BASH' +#!/usr/bin/env bash +if [ -n "${ANTIGRAVITY_AGENT:-}" ] || [ -n "${ANTIGRAVITY_CSRF_TOKEN:-}" ]; then + echo "ERROR: ANTIGRAVITY_ variables found" >&2 + exit 1 +fi +if [ "$AGY_MODE" != "plan" ]; then + echo "ERROR: AGY_MODE != plan" >&2 + exit 1 +fi +if [ -z "$HOME" ] || [ -z "$PATH" ]; then + echo "ERROR: HOME or PATH missing" >&2 + exit 1 +fi + +cat << JSON +{ + "ok": true, + "parsed": { + "task_id": "task-test-env-001", + "esito_205": "riuscito", + "repository": "ssh://git@192.168.0.53:2222/michele/netgescon-day0.git", + "branch": "stabilization/205-zero", + "commit": "0ac85f8c8502d9bb4e015ee6b15a452ef3fd1422", + "consolidated_unit_id": "SMOKE-24825CBB", + "blocco_dati": "no", + "dates_used": ["2026-08-10"], + "test_eseguiti": ["env test"], + "note": ["ENV_OK"], + "absorbed_legacy_fragments": ["frag-001"], + "open_legacy_fragments": [], + "raw_text": "ENV_OK" + } +} +JSON +exit 0 +BASH; + + file_put_contents($mockScript, $scriptContent); + chmod($mockScript, 0755); + + $exitCode = $this->artisan('netgescon:control-tower-poll', [ + '--task-id' => $taskId, + '--wrapper-script' => $mockScript, + ])->run(); + + @unlink($mockScript); + + expect($exitCode)->toBe(Command::SUCCESS); + + Http::assertSent(function ($request) use ($taskId) { + return str_contains($request->url(), '/api/reports') && + ($request['task_id'] ?? null) === $taskId && + str_contains($request['raw_text'] ?? '', 'ENV_OK'); + }); +}); + +it('does not publish report when wrapper exit code is non-zero', function () { + $taskId = 'task-test-err-001'; + + Http::fake([ + 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200), + ]); + + $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_err_') . '.sh'; + file_put_contents($mockScript, "#!/usr/bin/env bash\necho 'Fatal wrapper error' >&2\nexit 1\n"); + chmod($mockScript, 0755); + + $exitCode = $this->artisan('netgescon:control-tower-poll', [ + '--task-id' => $taskId, + '--wrapper-script' => $mockScript, + ])->run(); + + @unlink($mockScript); + + expect($exitCode)->toBe(Command::FAILURE); + + Http::assertNotSent(function ($request) { + return str_contains($request->url(), '/api/reports'); + }); +}); + +it('publishes report when wrapper returns valid parsed payload', function () { $taskId = 'task-test-001'; Http::fake([ @@ -28,10 +122,11 @@ 'commit' => 'a159c03799bd942bb6c8a74e50eb9ddf9a74aefb', 'consolidated_unit_id' => '1545', 'blocco_dati' => 'no', - 'dates_used' => '2026-08-10', - 'test_eseguiti' => 'NETTOWER_AGY_ULTRA_WRAPPER_OK', - 'note' => 'Test OK', - 'absorbed_legacy_fragments' => 'id_cond=12', + 'dates_used' => ['2026-08-10'], + 'test_eseguiti' => ['NETTOWER_AGY_ULTRA_WRAPPER_OK'], + 'note' => ['Test OK'], + 'absorbed_legacy_fragments' => ['id_cond=12'], + 'open_legacy_fragments' => [], 'raw_text' => 'NETTOWER_AGY_ULTRA_WRAPPER_OK', ], ], JSON_UNESCAPED_SLASHES); @@ -54,119 +149,3 @@ str_contains($request['raw_text'] ?? '', 'NETTOWER_AGY_ULTRA_WRAPPER_OK'); }); }); - -it('fails when wrapper returns old A/11 template non-JSON', function () { - $taskId = 'task-test-001'; - - Http::fake([ - 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200), - ]); - - $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_') . '.sh'; - $oldA11Report = "ESITO_205: riuscito\nTASK_ID: {$taskId}\nUNITA_ID_A11: 1749\nCODICE_BEFORE: 0021-A-220\nCODICE_AFTER: 0021-A-11"; - - file_put_contents($mockScript, "#!/usr/bin/env bash\ncat - >/dev/null\necho '{$oldA11Report}'\nexit 0\n"); - chmod($mockScript, 0755); - - $exitCode = $this->artisan('netgescon:control-tower-poll', [ - '--task-id' => $taskId, - '--wrapper-script' => $mockScript, - ])->run(); - - @unlink($mockScript); - - expect($exitCode)->toBe(Command::FAILURE); - - Http::assertNotSent(function ($request) { - return str_contains($request->url(), '/api/reports'); - }); -}); - -it('fails and does not publish report when wrapper exits with code 1', function () { - $taskId = 'task-test-001'; - - Http::fake([ - 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200), - ]); - - $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_') . '.sh'; - file_put_contents($mockScript, "#!/usr/bin/env bash\necho 'ERROR: CLI failed' >&2\nexit 1\n"); - chmod($mockScript, 0755); - - $exitCode = $this->artisan('netgescon:control-tower-poll', [ - '--task-id' => $taskId, - '--wrapper-script' => $mockScript, - ])->run(); - - @unlink($mockScript); - - expect($exitCode)->toBe(Command::FAILURE); - - Http::assertNotSent(function ($request) { - return str_contains($request->url(), '/api/reports'); - }); -}); - -it('fails and does not publish report when parsed task_id differs from current task', function () { - $taskId = 'task-test-001'; - - Http::fake([ - 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200), - ]); - - $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_') . '.sh'; - $jsonOutput = json_encode([ - 'ok' => true, - 'parsed' => [ - 'task_id' => 'DIFFERENT_TASK_999', - 'esito_205' => 'riuscito', - ], - ], JSON_UNESCAPED_SLASHES); - - file_put_contents($mockScript, "#!/usr/bin/env bash\necho '{$jsonOutput}'\nexit 0\n"); - chmod($mockScript, 0755); - - $exitCode = $this->artisan('netgescon:control-tower-poll', [ - '--task-id' => $taskId, - '--wrapper-script' => $mockScript, - ])->run(); - - @unlink($mockScript); - - expect($exitCode)->toBe(Command::FAILURE); - - Http::assertNotSent(function ($request) { - return str_contains($request->url(), '/api/reports'); - }); -}); - -it('fails and does not publish report when JSON ok=false', function () { - $taskId = 'task-test-001'; - - Http::fake([ - 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200), - ]); - - $mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_') . '.sh'; - $jsonOutput = json_encode([ - 'ok' => false, - 'missing' => ['task_id', 'esito_205'], - 'parsed' => [], - ], JSON_UNESCAPED_SLASHES); - - file_put_contents($mockScript, "#!/usr/bin/env bash\necho '{$jsonOutput}'\nexit 0\n"); - chmod($mockScript, 0755); - - $exitCode = $this->artisan('netgescon:control-tower-poll', [ - '--task-id' => $taskId, - '--wrapper-script' => $mockScript, - ])->run(); - - @unlink($mockScript); - - expect($exitCode)->toBe(Command::FAILURE); - - Http::assertNotSent(function ($request) { - return str_contains($request->url(), '/api/reports'); - }); -});