mirror of
https://github.com/alexjustesen/speedtest-tracker.git
synced 2026-06-23 04:30:09 +00:00
API requires accept json header (#2333)
Co-authored-by: Alex Justesen <1144087+alexjustesen@users.noreply.github.com> Co-authored-by: GitHub Action <actions@github.com>
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
|
||||||
|
|
||||||
|
class AcceptJsonMiddleware
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): SymfonyResponse
|
||||||
|
{
|
||||||
|
// Check if the Accept header includes application/json
|
||||||
|
if (! $request->acceptsJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'This endpoint only accepts JSON. Please include "Accept: application/json" in your request headers.',
|
||||||
|
'error' => 'Unsupported Media Type',
|
||||||
|
], Response::HTTP_NOT_ACCEPTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the response is JSON
|
||||||
|
$response = $next($request);
|
||||||
|
|
||||||
|
// Force JSON content type if not already set
|
||||||
|
if (! $response->headers->has('Content-Type') ||
|
||||||
|
! str_contains($response->headers->get('Content-Type'), 'application/json')) {
|
||||||
|
$response->headers->set('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ class OoklaAnnotations
|
|||||||
description: 'Returns an array of available Ookla speedtest servers. Requires an API token with `ookla:list-servers` scope.',
|
description: 'Returns an array of available Ookla speedtest servers. Requires an API token with `ookla:list-servers` scope.',
|
||||||
operationId: 'listOoklaServers',
|
operationId: 'listOoklaServers',
|
||||||
tags: ['Servers'],
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_OK,
|
response: Response::HTTP_OK,
|
||||||
@@ -33,6 +36,11 @@ class OoklaAnnotations
|
|||||||
description: 'Forbidden',
|
description: 'Forbidden',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
public function listServers(): void {}
|
public function listServers(): void {}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class ResultsAnnotations
|
|||||||
operationId: 'listResults',
|
operationId: 'listResults',
|
||||||
tags: ['Results'],
|
tags: ['Results'],
|
||||||
parameters: [
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
new OA\Parameter(
|
new OA\Parameter(
|
||||||
name: 'per_page',
|
name: 'per_page',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
@@ -104,6 +105,11 @@ class ResultsAnnotations
|
|||||||
description: 'Unauthenticated',
|
description: 'Unauthenticated',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||||
description: 'Validation failed',
|
description: 'Validation failed',
|
||||||
@@ -119,6 +125,7 @@ class ResultsAnnotations
|
|||||||
operationId: 'getResult',
|
operationId: 'getResult',
|
||||||
tags: ['Results'],
|
tags: ['Results'],
|
||||||
parameters: [
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
new OA\Parameter(
|
new OA\Parameter(
|
||||||
name: 'id',
|
name: 'id',
|
||||||
in: 'path',
|
in: 'path',
|
||||||
@@ -143,6 +150,11 @@ class ResultsAnnotations
|
|||||||
description: 'Unauthenticated',
|
description: 'Unauthenticated',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_NOT_FOUND,
|
response: Response::HTTP_NOT_FOUND,
|
||||||
description: 'Result not found',
|
description: 'Result not found',
|
||||||
@@ -157,6 +169,9 @@ class ResultsAnnotations
|
|||||||
summary: 'Get the most recent result',
|
summary: 'Get the most recent result',
|
||||||
operationId: 'getLatestResult',
|
operationId: 'getLatestResult',
|
||||||
tags: ['Results'],
|
tags: ['Results'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_OK,
|
response: Response::HTTP_OK,
|
||||||
@@ -173,6 +188,11 @@ class ResultsAnnotations
|
|||||||
description: 'Unauthenticated',
|
description: 'Unauthenticated',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_NOT_FOUND,
|
response: Response::HTTP_NOT_FOUND,
|
||||||
description: 'No result found',
|
description: 'No result found',
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class SpeedtestAnnotations
|
|||||||
operationId: 'runSpeedtest',
|
operationId: 'runSpeedtest',
|
||||||
tags: ['Speedtests'],
|
tags: ['Speedtests'],
|
||||||
parameters: [
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
new OA\Parameter(
|
new OA\Parameter(
|
||||||
name: 'server_id',
|
name: 'server_id',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
@@ -41,6 +42,11 @@ class SpeedtestAnnotations
|
|||||||
description: 'Forbidden',
|
description: 'Forbidden',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||||
description: 'Validation error',
|
description: 'Validation error',
|
||||||
@@ -58,6 +64,9 @@ class SpeedtestAnnotations
|
|||||||
summary: 'List available Ookla speedtest servers',
|
summary: 'List available Ookla speedtest servers',
|
||||||
operationId: 'listSpeedtestServers',
|
operationId: 'listSpeedtestServers',
|
||||||
tags: ['Speedtests'],
|
tags: ['Speedtests'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_OK,
|
response: Response::HTTP_OK,
|
||||||
@@ -77,6 +86,11 @@ class SpeedtestAnnotations
|
|||||||
example: ['message' => 'You do not have permission to view speedtest servers.']
|
example: ['message' => 'You do not have permission to view speedtest servers.']
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
public function listServers(): void {}
|
public function listServers(): void {}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class StatsAnnotations
|
|||||||
operationId: 'getStats',
|
operationId: 'getStats',
|
||||||
tags: ['Stats'],
|
tags: ['Stats'],
|
||||||
parameters: [
|
parameters: [
|
||||||
|
new OA\Parameter(ref: '#/components/parameters/AcceptHeader'),
|
||||||
new OA\Parameter(
|
new OA\Parameter(
|
||||||
name: 'start_at',
|
name: 'start_at',
|
||||||
in: 'query',
|
in: 'query',
|
||||||
@@ -48,6 +49,11 @@ class StatsAnnotations
|
|||||||
description: 'Forbidden',
|
description: 'Forbidden',
|
||||||
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError')
|
||||||
),
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: Response::HTTP_NOT_ACCEPTABLE,
|
||||||
|
description: 'Not Acceptable - Missing or invalid Accept header',
|
||||||
|
content: new OA\JsonContent(ref: '#/components/schemas/NotAcceptableError')
|
||||||
|
),
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
response: Response::HTTP_UNPROCESSABLE_ENTITY,
|
||||||
description: 'Validation error',
|
description: 'Validation error',
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ use OpenApi\Attributes as OA;
|
|||||||
],
|
],
|
||||||
parameters: [
|
parameters: [
|
||||||
new OA\Parameter(
|
new OA\Parameter(
|
||||||
|
parameter: 'AcceptHeader',
|
||||||
name: 'Accept',
|
name: 'Accept',
|
||||||
in: 'header',
|
in: 'header',
|
||||||
required: true,
|
required: true,
|
||||||
schema: new OA\Schema(type: 'string', default: 'application/json'),
|
schema: new OA\Schema(type: 'string', default: 'application/json'),
|
||||||
description: 'Expected response format'
|
description: 'Must be "application/json" - this API only accepts and returns JSON'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\OpenApi\Schemas;
|
||||||
|
|
||||||
|
use OpenApi\Attributes as OA;
|
||||||
|
|
||||||
|
#[OA\Schema(
|
||||||
|
schema: 'NotAcceptableError',
|
||||||
|
description: 'Error response when the Accept header is missing or invalid',
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
new OA\Property(
|
||||||
|
property: 'message',
|
||||||
|
type: 'string',
|
||||||
|
example: 'This endpoint only accepts JSON. Please include "Accept: application/json" in your request headers.'
|
||||||
|
),
|
||||||
|
new OA\Property(
|
||||||
|
property: 'error',
|
||||||
|
type: 'string',
|
||||||
|
example: 'Unsupported Media Type'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
class NotAcceptableErrorSchema {}
|
||||||
@@ -17,6 +17,7 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'getting-started' => App\Http\Middleware\GettingStarted::class,
|
'getting-started' => App\Http\Middleware\GettingStarted::class,
|
||||||
'public-dashboard' => App\Http\Middleware\PublicDashboard::class,
|
'public-dashboard' => App\Http\Middleware\PublicDashboard::class,
|
||||||
|
'accept-json' => App\Http\Middleware\AcceptJsonMiddleware::class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$middleware->prependToGroup('api', [
|
$middleware->prependToGroup('api', [
|
||||||
|
|||||||
+113
-2
@@ -17,6 +17,9 @@
|
|||||||
"summary": "List all results",
|
"summary": "List all results",
|
||||||
"operationId": "listResults",
|
"operationId": "listResults",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "per_page",
|
"name": "per_page",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -156,6 +159,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "Validation failed",
|
"description": "Validation failed",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -178,6 +191,9 @@
|
|||||||
"summary": "Fetch aggregated Speedtest statistics",
|
"summary": "Fetch aggregated Speedtest statistics",
|
||||||
"operationId": "getStats",
|
"operationId": "getStats",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "start_at",
|
"name": "start_at",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -230,6 +246,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "Validation error",
|
"description": "Validation error",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -251,6 +277,11 @@
|
|||||||
"summary": "List available Ookla speedtest servers",
|
"summary": "List available Ookla speedtest servers",
|
||||||
"description": "Returns an array of available Ookla speedtest servers. Requires an API token with `ookla:list-servers` scope.",
|
"description": "Returns an array of available Ookla speedtest servers. Requires an API token with `ookla:list-servers` scope.",
|
||||||
"operationId": "listOoklaServers",
|
"operationId": "listOoklaServers",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Servers retrieved successfully",
|
"description": "Servers retrieved successfully",
|
||||||
@@ -281,6 +312,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,6 +334,9 @@
|
|||||||
"summary": "Get a single result",
|
"summary": "Get a single result",
|
||||||
"operationId": "getResult",
|
"operationId": "getResult",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
@@ -334,6 +378,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Result not found",
|
"description": "Result not found",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -354,6 +408,11 @@
|
|||||||
],
|
],
|
||||||
"summary": "Get the most recent result",
|
"summary": "Get the most recent result",
|
||||||
"operationId": "getLatestResult",
|
"operationId": "getLatestResult",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -385,6 +444,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "No result found",
|
"description": "No result found",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -406,6 +475,9 @@
|
|||||||
"summary": "Run a new Ookla speedtest",
|
"summary": "Run a new Ookla speedtest",
|
||||||
"operationId": "runSpeedtest",
|
"operationId": "runSpeedtest",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "server_id",
|
"name": "server_id",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
@@ -447,6 +519,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"422": {
|
"422": {
|
||||||
"description": "Validation error",
|
"description": "Validation error",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -467,6 +549,11 @@
|
|||||||
],
|
],
|
||||||
"summary": "List available Ookla speedtest servers",
|
"summary": "List available Ookla speedtest servers",
|
||||||
"operationId": "listSpeedtestServers",
|
"operationId": "listSpeedtestServers",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/AcceptHeader"
|
||||||
|
}
|
||||||
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
@@ -500,6 +587,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"406": {
|
||||||
|
"description": "Not Acceptable - Missing or invalid Accept header",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/NotAcceptableError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -517,6 +614,20 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"NotAcceptableError": {
|
||||||
|
"description": "Error response when the Accept header is missing or invalid",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "This endpoint only accepts JSON. Please include \"Accept: application/json\" in your request headers."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Unsupported Media Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"NotFoundError": {
|
"NotFoundError": {
|
||||||
"description": "Error when a requested result is not found",
|
"description": "Error when a requested result is not found",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -1038,10 +1149,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Accept": {
|
"AcceptHeader": {
|
||||||
"name": "Accept",
|
"name": "Accept",
|
||||||
"in": "header",
|
"in": "header",
|
||||||
"description": "Expected response format",
|
"description": "Must be \"application/json\" - this API only accepts and returns JSON",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
+2
-1
@@ -20,8 +20,9 @@ Route::get('/healthcheck', function () {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
Route::get('/speedtest/latest', GetLatestController::class)
|
Route::get('/speedtest/latest', GetLatestController::class)
|
||||||
|
->middleware('accept-json')
|
||||||
->name('speedtest.latest');
|
->name('speedtest.latest');
|
||||||
|
|
||||||
Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
|
Route::middleware(['auth:sanctum', 'throttle:api', 'accept-json'])->group(function () {
|
||||||
require __DIR__.'/api/v1/routes.php';
|
require __DIR__.'/api/v1/routes.php';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware accepts requests without Accept header (Laravel default)', function () {
|
||||||
|
// Laravel's acceptsJson() returns true when no Accept header is present
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET');
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(200);
|
||||||
|
|
||||||
|
$content = json_decode($response->getContent(), true);
|
||||||
|
expect($content['success'])->toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware accepts requests with Accept: application/json header', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(200);
|
||||||
|
|
||||||
|
$content = json_decode($response->getContent(), true);
|
||||||
|
expect($content['success'])->toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware rejects requests with Accept: */json header', function () {
|
||||||
|
// Laravel's acceptsJson() returns false for */json
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => '*/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(406);
|
||||||
|
|
||||||
|
$content = json_decode($response->getContent(), true);
|
||||||
|
expect($content['message'])->toBe('This endpoint only accepts JSON. Please include "Accept: application/json" in your request headers.');
|
||||||
|
expect($content['error'])->toBe('Unsupported Media Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware accepts requests with Accept: application/* header', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'application/*',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware accepts requests with multiple Accept headers including application/json', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'text/html,application/json,application/xml;q=0.9,*/*;q=0.8',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware rejects requests with only non-JSON Accept headers', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'text/html,application/xml',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(406);
|
||||||
|
|
||||||
|
$content = json_decode($response->getContent(), true);
|
||||||
|
expect($content['message'])->toBe('This endpoint only accepts JSON. Please include "Accept: application/json" in your request headers.');
|
||||||
|
expect($content['error'])->toBe('Unsupported Media Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware sets Content-Type header to application/json when not already set', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->headers->get('Content-Type'))->toBe('application/json');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware preserves existing application/json Content-Type header', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'application/json',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->headers->get('Content-Type'))->toContain('application/json');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware rejects requests that only accept HTML', function () {
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => 'text/html',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(406);
|
||||||
|
|
||||||
|
$content = json_decode($response->getContent(), true);
|
||||||
|
expect($content['message'])->toBe('This endpoint only accepts JSON. Please include "Accept: application/json" in your request headers.');
|
||||||
|
expect($content['error'])->toBe('Unsupported Media Type');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('AcceptJsonMiddleware accepts requests with */* Accept header', function () {
|
||||||
|
// Laravel's acceptsJson() returns true for */*
|
||||||
|
$middleware = new \App\Http\Middleware\AcceptJsonMiddleware;
|
||||||
|
|
||||||
|
$request = \Illuminate\Http\Request::create('/api/test', 'GET', [], [], [], [
|
||||||
|
'HTTP_ACCEPT' => '*/*',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $middleware->handle($request, function () {
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($response->getStatusCode())->toBe(200);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user