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([ 'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId, 'title' => 'Test Task', 'description' => 'Test Desc']], 200), 'http://192.168.0.53:4174/api/reports' => Http::response(['id' => 'rep-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_') . '.sh'; $jsonOutput = json_encode([ 'ok' => true, 'parsed' => [ 'task_id' => $taskId, 'esito_205' => 'riuscito', 'repository' => 'ssh://git@192.168.0.53:2222/michele/netgescon-day0.git', 'branch' => 'stabilization/205-zero', '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'], 'open_legacy_fragments' => [], 'raw_text' => 'NETTOWER_AGY_ULTRA_WRAPPER_OK', ], ], JSON_UNESCAPED_SLASHES); file_put_contents($mockScript, "#!/usr/bin/env bash\ncat - >/dev/null\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::SUCCESS); Http::assertSent(function ($request) use ($taskId) { return str_contains($request->url(), '/api/reports') && ($request['task_id'] ?? null) === $taskId && str_contains($request['raw_text'] ?? '', 'NETTOWER_AGY_ULTRA_WRAPPER_OK'); }); });