add test batches
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\V1\FullSurface;
|
||||
|
||||
use Illuminate\Routing\Route as LaravelRoute;
|
||||
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
||||
|
||||
class ApiDeepValidationFailureContractTest extends FullSurfaceE2EContractCase
|
||||
{
|
||||
public function test_mutating_routes_return_controlled_validation_or_authorization_for_hostile_payloads(): void
|
||||
{
|
||||
$failures = [];
|
||||
|
||||
foreach ($this->mutatingRoutes() as $route) {
|
||||
$method = $this->primaryMethod($route);
|
||||
$uri = $route->uri();
|
||||
|
||||
foreach ($this->hostilePayloads() as $label => $payload) {
|
||||
$response = $this->requestAs($this->actorFor($uri), $method, $uri, $payload);
|
||||
$status = $response->getStatusCode();
|
||||
|
||||
if ($status >= 500) {
|
||||
$failures[] = "$method $uri crashed on $label hostile payload with $status: " . $response->getContent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! in_array($status, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], true)) {
|
||||
$failures[] = "$method $uri returned unexpected $status on $label hostile payload: " . $response->getContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $failures, "Hostile payloads must fail cleanly, not leak framework explosions:\n" . implode("\n", $failures));
|
||||
}
|
||||
|
||||
public function test_required_field_validation_uses_machine_readable_error_shape_when_validation_fails(): void
|
||||
{
|
||||
$failures = [];
|
||||
|
||||
foreach ($this->validationSensitiveRoutes() as $route) {
|
||||
$method = $this->primaryMethod($route);
|
||||
$uri = $route->uri();
|
||||
$response = $this->requestAs($this->actorFor($uri), $method, $uri, []);
|
||||
|
||||
if ($response->getStatusCode() !== 422) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! $this->isJsonString($response->getContent())) {
|
||||
$failures[] = "$method $uri returned 422 but not JSON: " . $response->getContent();
|
||||
continue;
|
||||
}
|
||||
|
||||
$json = $response->json();
|
||||
if (! is_array($json) || (! array_key_exists('errors', $json) && ! array_key_exists('message', $json))) {
|
||||
$failures[] = "$method $uri returned 422 without message/errors: " . $response->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $failures, "Validation errors must be usable by clients, not poetic prose:\n" . implode("\n", $failures));
|
||||
}
|
||||
|
||||
/** @return list<LaravelRoute> */
|
||||
private function mutatingRoutes(): array
|
||||
{
|
||||
return array_slice(array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
|
||||
return in_array($this->primaryMethod($route), ['POST', 'PUT', 'PATCH', 'DELETE'], true)
|
||||
&& ! str_contains($route->uri(), 'auth/logout');
|
||||
})), 0, 150);
|
||||
}
|
||||
|
||||
/** @return list<LaravelRoute> */
|
||||
private function validationSensitiveRoutes(): array
|
||||
{
|
||||
return array_values(array_filter($this->mutatingRoutes(), function (LaravelRoute $route): bool {
|
||||
$uri = $route->uri();
|
||||
|
||||
return str_contains($uri, 'students')
|
||||
|| str_contains($uri, 'users')
|
||||
|| str_contains($uri, 'attendance')
|
||||
|| str_contains($uri, 'finance')
|
||||
|| str_contains($uri, 'scores')
|
||||
|| str_contains($uri, 'support')
|
||||
|| str_contains($uri, 'inventory');
|
||||
}));
|
||||
}
|
||||
|
||||
/** @return array<string, array<string, mixed>> */
|
||||
private function hostilePayloads(): array
|
||||
{
|
||||
return [
|
||||
'empty' => [],
|
||||
'wrong_scalar_types' => ['id' => 'not-an-int', 'amount' => 'NaN', 'date' => 'not-a-date', 'email' => 'not-email'],
|
||||
'unexpected_nested_arrays' => ['name' => ['nested' => ['too' => ['deep' => true]]], 'student_id' => ['x']],
|
||||
'oversized_strings' => ['name' => str_repeat('x', 1024), 'description' => str_repeat('y', 2048)],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user