diff --git a/app/Services/SentinelTrafficClient.php b/app/Services/SentinelTrafficClient.php index cbbf9f971c..89c24bbbbe 100644 --- a/app/Services/SentinelTrafficClient.php +++ b/app/Services/SentinelTrafficClient.php @@ -729,11 +729,9 @@ class SentinelTrafficClient { $token = $this->server->settings->ensureValidSentinelToken(); - return instant_remote_process( - [$this->buildFetchCommand($token, $url)], - $this->server, - false - ); + // Throw the real SSH/docker error (for example, an unreadable SSH key or a missing + // container). A literal "null" body comes back as null; guard() rejects the empty string. + return instant_remote_process([$this->buildFetchCommand($token, $url)], $this->server) ?? ''; } /** @@ -743,11 +741,7 @@ class SentinelTrafficClient { $token = $this->server->settings->ensureValidSentinelToken(); - return instant_remote_process( - [$this->buildBatchCommand($token, $urls)], - $this->server, - false - ); + return instant_remote_process([$this->buildBatchCommand($token, $urls)], $this->server) ?? ''; } /** @@ -762,7 +756,7 @@ class SentinelTrafficClient */ protected function buildFetchCommand(string $token, string $url): string { - return "docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$token}\" \"{$url}\"'"; + return "docker exec coolify-sentinel sh -c 'curl -sS -H \"Authorization: Bearer {$token}\" \"{$url}\"'"; } /** diff --git a/tests/Feature/TrafficAnalytics/SentinelTrafficCacheTest.php b/tests/Feature/TrafficAnalytics/SentinelTrafficCacheTest.php index 1eb0329c99..408c53ee26 100644 --- a/tests/Feature/TrafficAnalytics/SentinelTrafficCacheTest.php +++ b/tests/Feature/TrafficAnalytics/SentinelTrafficCacheTest.php @@ -1,10 +1,12 @@ overview('app', 'a', 'b'); expect($client->calls)->toBe(1); }); + +it('surfaces the real remote error instead of a TypeError when the fetch fails', function (int $exitCode, string $output, string $errorOutput, string $message) { + Cache::flush(); + config()->set('constants.ssh.max_retries', 1); + Process::fake(fn () => Process::result(output: $output, errorOutput: $errorOutput, exitCode: $exitCode)); + $team = Team::factory()->create(); + $server = Server::factory()->create([ + 'team_id' => $team->id, + 'private_key_id' => PrivateKey::factory()->create(['team_id' => $team->id])->id, + ]); + + expect(fn () => (new SentinelTrafficClient($server))->overview('app', 'a', 'b')) + ->toThrow(RuntimeException::class, $message); +})->with([ + 'failed docker exec' => [1, '', 'Error response from daemon: No such container: coolify-sentinel', 'No such container: coolify-sentinel'], + 'literal null body' => [0, 'null', '', 'Traffic analytics returned an invalid response.'], +]);