diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 392b1fd4c0..8fa3125aec 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; @@ -281,10 +282,17 @@ class OtherController extends Controller $webhook_url = config('constants.webhooks.feedback_discord_webhook'); if ($webhook_url) { - Http::timeout(5)->post($webhook_url, [ - 'content' => $data['content'], - 'allowed_mentions' => ['parse' => []], - ]); + try { + $response = Http::timeout(5)->post($webhook_url, [ + 'content' => $data['content'], + 'allowed_mentions' => ['parse' => []], + ]); + } catch (ConnectionException) { + $response = null; + } + if (! $response?->successful()) { + return response()->json(['message' => 'Feedback could not be delivered.'], 502); + } } return response()->json(['message' => 'Feedback sent.'], 200); diff --git a/app/Livewire/Help.php b/app/Livewire/Help.php index 421e50bcca..9c18e21d30 100644 --- a/app/Livewire/Help.php +++ b/app/Livewire/Help.php @@ -3,6 +3,7 @@ namespace App\Livewire; use DanHarrin\LivewireRateLimiting\WithRateLimiting; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Http; use Livewire\Attributes\Validate; @@ -38,9 +39,18 @@ class Help extends Component // Sending feedback through Cloud API if (blank($type)) { $url = 'https://app.coolify.io/api/feedback'; - Http::post($url, [ - 'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`', - ]); + try { + $response = Http::timeout(10)->post($url, [ + 'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`', + ]); + } catch (ConnectionException) { + $response = null; + } + if (! $response?->successful()) { + $this->dispatch('error', 'Feedback could not be sent.', 'Please try again later or contact us on Discord.'); + + return; + } } else { send_user_an_email($mail, auth()->user()?->email, 'feedback@coollabs.io'); } diff --git a/tests/Feature/FeedbackEndpointTest.php b/tests/Feature/FeedbackEndpointTest.php index a2c603defb..ef050ca62b 100644 --- a/tests/Feature/FeedbackEndpointTest.php +++ b/tests/Feature/FeedbackEndpointTest.php @@ -94,3 +94,14 @@ it('disables discord mention parsing regardless of content', function () { return $request['allowed_mentions'] === ['parse' => []]; }); }); + +it('returns an error when the discord webhook fails', function () { + config()->set('constants.webhooks.feedback_discord_webhook', 'https://webhook.example.com/failing'); + Http::fake(['webhook.example.com/*' => Http::response([], 500)]); + + $response = $this->postJson('/api/feedback', [ + 'content' => 'This is a valid feedback message for testing purposes.', + ]); + + $response->assertStatus(502); +}); diff --git a/tests/Feature/HelpFeedbackTest.php b/tests/Feature/HelpFeedbackTest.php new file mode 100644 index 0000000000..0b34f29b24 --- /dev/null +++ b/tests/Feature/HelpFeedbackTest.php @@ -0,0 +1,52 @@ + 0]); + $this->actingAs(User::factory()->create()); +}); + +it('shows success when the cloud api accepts feedback without email configured', function () { + Http::fake(['app.coolify.io/*' => Http::response(['message' => 'Feedback sent.'], 200)]); + + Livewire::test(Help::class) + ->set('subject', 'Test subject') + ->set('description', 'This is a detailed feedback description.') + ->call('submit') + ->assertDispatched('success') + ->assertNotDispatched('error'); + + Http::assertSent(fn ($request) => $request->url() === 'https://app.coolify.io/api/feedback'); +}); + +it('shows an error when the cloud api rejects feedback without email configured', function (int $status) { + Http::fake(['app.coolify.io/*' => Http::response([], $status)]); + + Livewire::test(Help::class) + ->set('subject', 'Test subject') + ->set('description', 'This is a detailed feedback description.') + ->call('submit') + ->assertDispatched('error') + ->assertNotDispatched('success') + ->assertSet('subject', 'Test subject'); +})->with([422, 429, 500, 502]); + +it('shows an error when the cloud api is unreachable', function () { + Http::fake(fn () => throw new ConnectionException('Connection refused')); + + Livewire::test(Help::class) + ->set('subject', 'Test subject') + ->set('description', 'This is a detailed feedback description.') + ->call('submit') + ->assertDispatched('error') + ->assertNotDispatched('success'); +});