fix(ops): fetch real task details via GET /api/tasks/{taskId} and prevent synthetic task fallbacks
This commit is contained in:
parent
27b8f34392
commit
86928f069b
|
|
@ -31,7 +31,11 @@ public function handle(): int
|
|||
|
||||
$task = null;
|
||||
if ($taskIdOpt) {
|
||||
$task = ['id' => $taskIdOpt, 'title' => "Task {$taskIdOpt}", 'description' => "Esecuzione manuale task {$taskIdOpt}"];
|
||||
$task = $this->fetchTaskById($towerBase, $taskIdOpt, $machineId, $token);
|
||||
if (! $task) {
|
||||
$this->error("❌ RECUPERO TASK REALE FALLITO: Task {$taskIdOpt} non trovato o errore HTTP. Nessun fallback sintetico ammesso.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
} else {
|
||||
$task = $this->fetchNextTask($towerBase, $machineId, $token);
|
||||
if (! $task) {
|
||||
|
|
@ -39,10 +43,37 @@ public function handle(): int
|
|||
$task = $this->fetchCurrentTaskFromPacket($towerBase, $machineId, $token);
|
||||
}
|
||||
}
|
||||
$taskId = $task['id'] ?? 'task-simulated-runner-test';
|
||||
$title = $task['title'] ?? 'Task di Test Audit Cespiti';
|
||||
$desc = $task['description'] ?? 'Esecuzione automatica via Control Tower Poller';
|
||||
$meta = $task['metadata'] ?? [];
|
||||
|
||||
if (! $task || ! is_array($task)) {
|
||||
$this->error('❌ TASK NON VALIDO O MANCANTE.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$taskId = $task['id'] ?? null;
|
||||
if (! $taskId) {
|
||||
$this->error('❌ TASK NON VALIDO: Campo id mancante.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($taskIdOpt && $taskId !== $taskIdOpt) {
|
||||
$this->error("❌ DISALLINEAMENTO TASK_ID: ID restituito '{$taskId}' non coincide con ID richiesto '{$taskIdOpt}'.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$assignedMachine = $task['target_machine_id'] ?? $task['assigned_machine_id'] ?? $task['machine_id'] ?? null;
|
||||
if ($assignedMachine !== null && $assignedMachine !== $machineId && $assignedMachine !== 'machine-205-dev') {
|
||||
$this->error("❌ MACCHINA DISALLINEATA: Task {$taskId} e assegnato a '{$assignedMachine}', non a '{$machineId}'.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$title = $task['title'] ?? null;
|
||||
$desc = $task['description'] ?? null;
|
||||
$meta = $task['metadata'] ?? null;
|
||||
|
||||
if ($title === null || $desc === null || $meta === null) {
|
||||
$this->error("❌ TASK INCOMPLETO: title, description o metadata mancanti per task {$taskId}.");
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (isset($task['status']) && in_array($task['status'], ['done', 'blocked'], true)) {
|
||||
$this->info("ℹ️ Task {$taskId} e gia in stato '{$task['status']}'. Esecuzione ignorata (idempotenza).");
|
||||
|
|
@ -60,12 +91,20 @@ public function handle(): int
|
|||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$promptInput = implode("\n", [
|
||||
$expectedReport = is_array($meta) ? ($meta['expectedReport'] ?? $meta['expected_report'] ?? null) : null;
|
||||
|
||||
$promptParts = [
|
||||
"TASK_ID: {$taskId}",
|
||||
"TITOLO: {$title}",
|
||||
"DESCRIZIONE:\n{$desc}",
|
||||
"METADATA:\n" . json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
|
||||
]);
|
||||
"METADATA:\n" . (is_array($meta) ? json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : $meta),
|
||||
];
|
||||
|
||||
if ($expectedReport !== null) {
|
||||
$promptParts[] = "EXPECTED_REPORT:\n" . (is_array($expectedReport) || is_object($expectedReport) ? json_encode($expectedReport, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) : $expectedReport);
|
||||
}
|
||||
|
||||
$promptInput = implode("\n\n", $promptParts);
|
||||
|
||||
$defaultWrapperScript = '/home/michele/netgescon-day0-backup/scripts/ops/antigravity-cli/run_205_followup_via_agy.sh';
|
||||
$wrapperScript = (string) ($this->option('wrapper-script') ?: $defaultWrapperScript);
|
||||
|
|
@ -201,6 +240,25 @@ private function loadMachineEnv(): array
|
|||
return $vars;
|
||||
}
|
||||
|
||||
private function fetchTaskById(string $towerBase, string $taskId, string $machineId, string $token): ?array
|
||||
{
|
||||
try {
|
||||
$response = Http::timeout(10)->get("{$towerBase}/api/tasks/{$taskId}", [
|
||||
'machine_id' => $machineId,
|
||||
'token' => $token,
|
||||
]);
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
return $data['task'] ?? ($data['id'] ?? null ? $data : null);
|
||||
} else {
|
||||
$this->error("❌ HTTP {$response->status()} durante il recupero del task {$taskId}");
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->error("❌ Errore eccezione recupero task {$taskId}: {$e->getMessage()}");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function fetchNextTask(string $towerBase, string $machineId, string $token): ?array
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -7,30 +7,45 @@
|
|||
Http::preventStrayRequests();
|
||||
});
|
||||
|
||||
it('verifies process environment HOME PATH AGY_MODE, absolute path, and preserves stdout/stderr', function () {
|
||||
$taskId = 'task-test-env-001';
|
||||
it('fetches real task via GET 200 and passes description, metadata, and expectedReport to wrapper', function () {
|
||||
$taskId = 'task-test-real-200';
|
||||
|
||||
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/task-test-real-200*' => Http::response([
|
||||
'task' => [
|
||||
'id' => $taskId,
|
||||
'title' => 'Real Title 200',
|
||||
'description' => 'Real Description 200 Content',
|
||||
'target_machine_id' => 'machine-205-dev',
|
||||
'metadata' => [
|
||||
'opId' => 'op-123',
|
||||
'expectedReport' => [
|
||||
'consolidated_unit_id' => 'UNIT-200',
|
||||
'note_contains' => 'NOTE_200_OK'
|
||||
]
|
||||
]
|
||||
]
|
||||
], 200),
|
||||
'http://192.168.0.53:4174/api/reports' => Http::response(['id' => 'rep-200'], 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';
|
||||
$mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_real_') . '.sh';
|
||||
|
||||
$scriptContent = <<<'BASH'
|
||||
#!/usr/bin/env bash
|
||||
if [ -n "${ANTIGRAVITY_AGENT:-}" ] || [ -n "${ANTIGRAVITY_CSRF_TOKEN:-}" ]; then
|
||||
echo "ERROR: ANTIGRAVITY_ variables found" >&2
|
||||
INPUT="$(cat -)"
|
||||
if [[ "$INPUT" != *"Real Description 200 Content"* ]]; then
|
||||
echo "ERROR: description missing from stdin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$AGY_MODE" != "plan" ]; then
|
||||
echo "ERROR: AGY_MODE != plan" >&2
|
||||
if [[ "$INPUT" != *"EXPECTED_REPORT:"* ]]; then
|
||||
echo "ERROR: expectedReport missing from stdin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$HOME" ] || [ -z "$PATH" ]; then
|
||||
echo "ERROR: HOME or PATH missing" >&2
|
||||
if [[ "$INPUT" != *"UNIT-200"* ]]; then
|
||||
echo "ERROR: UNIT-200 missing from stdin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -38,19 +53,19 @@
|
|||
{
|
||||
"ok": true,
|
||||
"parsed": {
|
||||
"task_id": "task-test-env-001",
|
||||
"task_id": "task-test-real-200",
|
||||
"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",
|
||||
"consolidated_unit_id": "UNIT-200",
|
||||
"blocco_dati": "no",
|
||||
"dates_used": ["2026-08-10"],
|
||||
"test_eseguiti": ["env test"],
|
||||
"note": ["ENV_OK"],
|
||||
"absorbed_legacy_fragments": ["frag-001"],
|
||||
"test_eseguiti": ["real task test"],
|
||||
"note": ["NOTE_200_OK"],
|
||||
"absorbed_legacy_fragments": ["frag-200"],
|
||||
"open_legacy_fragments": [],
|
||||
"raw_text": "ENV_OK"
|
||||
"raw_text": "NOTE_200_OK"
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
|
@ -69,10 +84,97 @@
|
|||
|
||||
expect($exitCode)->toBe(Command::SUCCESS);
|
||||
|
||||
Http::assertSent(function ($request) use ($taskId) {
|
||||
return str_contains($request->url(), '/api/tasks/' . $taskId);
|
||||
});
|
||||
|
||||
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');
|
||||
str_contains($request['raw_text'] ?? '', 'NOTE_200_OK');
|
||||
});
|
||||
});
|
||||
|
||||
it('fails and does not execute wrapper or publish report when task GET returns 403', function () {
|
||||
$taskId = 'task-test-403';
|
||||
|
||||
Http::fake([
|
||||
'http://192.168.0.53:4174/api/tasks/task-test-403*' => Http::response(['error' => 'Forbidden'], 403),
|
||||
]);
|
||||
|
||||
$mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_403_') . '.sh';
|
||||
file_put_contents($mockScript, "#!/usr/bin/env bash\necho 'Should not execute' >&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 execute wrapper or publish report when task GET returns 404', function () {
|
||||
$taskId = 'task-test-404';
|
||||
|
||||
Http::fake([
|
||||
'http://192.168.0.53:4174/api/tasks/task-test-404*' => Http::response(['error' => 'Not Found'], 404),
|
||||
]);
|
||||
|
||||
$mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_404_') . '.sh';
|
||||
file_put_contents($mockScript, "#!/usr/bin/env bash\necho 'Should not execute' >&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 when returned task ID differs from requested task ID', function () {
|
||||
$taskId = 'task-test-mismatch';
|
||||
|
||||
Http::fake([
|
||||
'http://192.168.0.53:4174/api/tasks/task-test-mismatch*' => Http::response([
|
||||
'task' => [
|
||||
'id' => 'different-id-999',
|
||||
'title' => 'Title',
|
||||
'description' => 'Desc',
|
||||
'target_machine_id' => 'machine-205-dev',
|
||||
'metadata' => []
|
||||
]
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_mismatch_') . '.sh';
|
||||
file_put_contents($mockScript, "#!/usr/bin/env bash\necho 'Should not execute' >&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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -80,7 +182,15 @@
|
|||
$taskId = 'task-test-err-001';
|
||||
|
||||
Http::fake([
|
||||
'http://192.168.0.53:4174/api/tasks/next*' => Http::response(['task' => ['id' => $taskId]], 200),
|
||||
'http://192.168.0.53:4174/api/tasks/task-test-err-001*' => Http::response([
|
||||
'task' => [
|
||||
'id' => $taskId,
|
||||
'title' => 'Title Err',
|
||||
'description' => 'Desc Err',
|
||||
'target_machine_id' => 'machine-205-dev',
|
||||
'metadata' => []
|
||||
]
|
||||
], 200),
|
||||
]);
|
||||
|
||||
$mockScript = tempnam(sys_get_temp_dir(), 'mock_wrapper_err_') . '.sh';
|
||||
|
|
@ -116,52 +226,3 @@
|
|||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user