diff --git a/ProcessMaker/Http/Controllers/Api/DevLinkController.php b/ProcessMaker/Http/Controllers/Api/DevLinkController.php index 8994184d1a..c319f76f7a 100644 --- a/ProcessMaker/Http/Controllers/Api/DevLinkController.php +++ b/ProcessMaker/Http/Controllers/Api/DevLinkController.php @@ -3,6 +3,7 @@ namespace ProcessMaker\Http\Controllers\Api; use Illuminate\Database\Eloquent\ModelNotFoundException; +use Illuminate\Http\Client\RequestException; use Illuminate\Http\Request; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Notification; @@ -91,10 +92,20 @@ public function destroy(DevLink $devLink) public function ping(DevLink $devLink) { try { - return $devLink->client()->get(route('api.devlink.pong', [], false)); - } catch (\Exception $e) { - return response()->json(['error' => 'DevLink connection error'], $e->getCode()); + $response = $devLink->client()->get(route('api.devlink.pong', [], false)); + } catch (RequestException $e) { + $status = $e->response->status(); + + return response()->json([ + 'status' => in_array($status, [401, 403], true) ? 'authorization_required' : 'error', + ]); + } catch (\Throwable $e) { + return response()->json(['status' => 'error']); } + + return response()->json([ + 'status' => $response->json('status') === 'ok' ? 'ok' : 'error', + ]); } public function pong() diff --git a/resources/js/admin/devlink/components/Index.vue b/resources/js/admin/devlink/components/Index.vue index 3f5e0e962f..c82db806ca 100644 --- a/resources/js/admin/devlink/components/Index.vue +++ b/resources/js/admin/devlink/components/Index.vue @@ -101,16 +101,15 @@ const create = (name, url) => { ProcessMaker.apiClient .get(`devlink/${newId}/ping`) .then((response) => { - status.value = 'success'; - window.location.href = fullUrl; - }) - .catch((e) => { - if (e.response.status === 401) { + if (['ok', 'authorization_required'].includes(response.data.status)) { status.value = 'success'; window.location.href = fullUrl; } else { status.value = 'error'; } + }) + .catch(() => { + status.value = 'error'; }); }); }; diff --git a/tests/Feature/Api/DevLinkTest.php b/tests/Feature/Api/DevLinkTest.php index 9bed587b88..0f96a75fe1 100644 --- a/tests/Feature/Api/DevLinkTest.php +++ b/tests/Feature/Api/DevLinkTest.php @@ -2,6 +2,7 @@ namespace Tests\Feature\Api; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Facades\Http; use ProcessMaker\Http\Controllers\Api\DevLinkController; use ProcessMaker\Models\Bundle; @@ -43,6 +44,93 @@ public function testAddAssets() $this->assertEquals('Asset already exists in bundle', $response->json()['error']['message']); } + public function testPingReturnsOkWhenRemotePongSucceeds() + { + $devLink = DevLink::factory()->create([ + 'url' => 'https://remote-instance.test', + 'access_token' => 'token', + ]); + + Http::fake([ + 'remote-instance.test/*' => Http::response(['status' => 'ok'], 200), + ]); + + $response = $this->apiCall('GET', route('api.devlink.ping', ['devLink' => $devLink->id])); + + $response->assertStatus(200); + $response->assertJson(['status' => 'ok']); + } + + public function testPingReturnsAuthorizationRequiredWhenRemotePongReturnsUnauthorized() + { + $devLink = DevLink::factory()->create([ + 'url' => 'https://remote-instance.test', + 'access_token' => 'token', + ]); + + Http::fake([ + 'remote-instance.test/*' => Http::response(['message' => 'Unauthorized'], 401), + ]); + + $response = $this->apiCall('GET', route('api.devlink.ping', ['devLink' => $devLink->id])); + + $response->assertStatus(200); + $response->assertJson(['status' => 'authorization_required']); + } + + public function testPingReturnsAuthorizationRequiredWhenRemotePongReturnsForbidden() + { + $devLink = DevLink::factory()->create([ + 'url' => 'https://remote-instance.test', + 'access_token' => 'token', + ]); + + Http::fake([ + 'remote-instance.test/*' => Http::response(['message' => 'Forbidden'], 403), + ]); + + $response = $this->apiCall('GET', route('api.devlink.ping', ['devLink' => $devLink->id])); + + $response->assertStatus(200); + $response->assertJson(['status' => 'authorization_required']); + } + + public function testPingReturnsErrorWhenRemotePongFails() + { + $devLink = DevLink::factory()->create([ + 'url' => 'https://remote-instance.test', + 'access_token' => 'token', + ]); + + Http::fake([ + 'remote-instance.test/*' => Http::response(['message' => 'Server error'], 500), + ]); + + $response = $this->apiCall('GET', route('api.devlink.ping', ['devLink' => $devLink->id])); + + $response->assertStatus(200); + $response->assertJson(['status' => 'error']); + } + + public function testPingReturnsErrorWhenRemotePongCannotConnect() + { + $devLink = DevLink::factory()->create([ + 'url' => 'https://remote-instance.test', + 'access_token' => 'token', + ]); + + Http::fake([ + 'remote-instance.test/*' => function () { + throw new ConnectionException('Connection failed'); + }, + ]); + + $response = $this->apiCall('GET', route('api.devlink.ping', ['devLink' => $devLink->id])); + + $response->assertStatus(200); + $response->assertJson(['status' => 'error']); + } + public function testInstallRemoteAsset() { $screen = Screen::factory()->create();