Skip to content

RequestController@latest fails with 500 Fatal Error on empty tokens (Missing NotFoundHttpException import & invalid Collection emptiness check) #196

Description

@codeCraft-Ritik

📋 Overview

When querying the latest captured request for a newly created token (or any token that has not yet received requests) using the endpoint GET /token/{tokenId}/request/latest, the application returns a 500 Internal Server Error instead of an expected 404 Not Found JSON response.


🔍 Root Cause Analysis

The bug resides in app/Http/Controllers/RequestController.php inside the latest() method (lines 185–201):

/**
 * Get the latest request associated with a token.
 *
 * @param HttpRequest $httpRequest
 * @param string $tokenId
 * @return JsonResponse
 *
 * @throws NotFoundHttpException
 */
public function latest(HttpRequest $httpRequest, string $tokenId): JsonResponse
{
    $token = $this->tokens->find($tokenId);

    $requests = $this->requests->all(
        $token,
        1,
        1,
        'newest'
    );

    if (empty($requests)) {
        throw new NotFoundHttpException('Request not found');
    }

    return new JsonResponse($requests[0]);
}

There are two distinct defects in this implementation:

1. Ineffective Emptiness Check on Illuminate\Support\Collection

  • The method $this->requests->all($token, 1, 1, 'newest') returns an instance of Illuminate\Support\Collection.
  • In PHP, calling empty($object) on any instantiated object always evaluates to false, even if the collection contains zero elements.
  • Because empty($requests) evaluates to false, the if guard is skipped when there are no requests. Execution proceeds to return new JsonResponse($requests[0]);. Accessing offset 0 on an empty collection attempts to read an uninitialized array offset (null or Undefined offset: 0), resulting in unintended behavior.

2. Missing Namespace Import for NotFoundHttpException

  • The docblock specifies @throws NotFoundHttpException, but NotFoundHttpException is not imported in RequestController.php via a use statement.
  • When throw new NotFoundHttpException('Request not found'); is reached, PHP attempts to instantiate App\Http\Controllers\NotFoundHttpException.
  • Since this class does not exist in the controller's namespace, a fatal error is raised:
    Fatal error: Uncaught Error: Class "App\Http\Controllers\NotFoundHttpException" not found in /var/www/html/app/Http/Controllers/RequestController.php
    
  • In production mode, this causes Laravel's exception handler to return a 500 Internal Server Error ({"success": false, "error": {"message": "An internal error occurred"}}).

🔁 Steps to Reproduce

  1. Create a fresh token:

    curl -s -X POST http://localhost:8084/token

    Example Response:

    {"uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", ...}
  2. Query the latest request before sending any webhooks to it:

    curl -i http://localhost:8084/token/3fa85f64-5717-4562-b3fc-2c963f66afa6/request/latest
  3. Observe the result:

    • Actual Response: HTTP/1.1 500 Internal Server Error
    • Expected Response: HTTP/1.1 404 Not Found with {"success": false, "error": {"message": "Request not found"}}

🛠️ Proposed Fix / Patch

1. Add Missing Import to app/Http/Controllers/RequestController.php:

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

2. Replace empty($requests) and $requests[0] with Collection Methods:

Use $requests->isEmpty() and $requests->first().

Unified Diff:

--- a/app/Http/Controllers/RequestController.php
+++ b/app/Http/Controllers/RequestController.php
@@ -11,6 +11,7 @@
 use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request as HttpRequest;
 use Illuminate\Http\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 class RequestController extends Controller
 {
@@ -193,11 +194,11 @@ public function latest(HttpRequest $httpRequest, string $tokenId): JsonResponse
             'newest'
         );
 
-        if (empty($requests)) {
+        if ($requests->isEmpty()) {
             throw new NotFoundHttpException('Request not found');
         }
 
-        return new JsonResponse($requests[0]);
+        return new JsonResponse($requests->first());
     }
 }

🧪 Unit Test

This test can be added to tests/RequestControllerTest.php to verify the fix and prevent regressions:

public function testLatestRequestNotFoundReturns404()
{
    $this->withoutMiddleware();

    $tokenResponse = $this->json('POST', 'token');
    $tokenId = $tokenResponse->json()['uuid'];

    // Requesting latest for a token with 0 requests should return 404
    $response = $this->json('GET', "token/{$tokenId}/request/latest");

    $response->assertStatus(404);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions