add test batches
This commit is contained in:
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Api\V1\FullSurface;
|
||||
|
||||
use Illuminate\Routing\Route as LaravelRoute;
|
||||
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;
|
||||
|
||||
class ApiDeepIdempotencyAndDuplicateSubmissionContractTest extends FullSurfaceE2EContractCase
|
||||
{
|
||||
public function test_duplicate_sensitive_mutations_do_not_create_uncontrolled_server_errors(): void
|
||||
{
|
||||
$failures = [];
|
||||
|
||||
foreach ($this->duplicateSensitiveRoutes() as $route) {
|
||||
$method = $this->primaryMethod($route);
|
||||
$uri = $route->uri();
|
||||
$payload = $this->payloadFor($method, $uri) + [
|
||||
'external_reference' => 'E2E-DUP-' . md5($uri),
|
||||
'idempotency_key' => 'E2E-DUP-' . md5($uri),
|
||||
];
|
||||
|
||||
$first = $this->requestAs($this->actorFor($uri), $method, $uri, $payload);
|
||||
$second = $this->requestAs($this->actorFor($uri), $method, $uri, $payload);
|
||||
|
||||
foreach (['first' => $first, 'second' => $second] as $label => $response) {
|
||||
$status = $response->getStatusCode();
|
||||
|
||||
if ($status >= 500) {
|
||||
$failures[] = "$method $uri $label duplicate request crashed with $status: " . $response->getContent();
|
||||
}
|
||||
|
||||
if (! in_array($status, [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422], true)) {
|
||||
$failures[] = "$method $uri $label duplicate request returned unexpected $status: " . $response->getContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $failures, "Duplicate submissions need controlled behavior, because users double-click like it's a sport:\n" . implode("\n", $failures));
|
||||
}
|
||||
|
||||
public function test_repeat_delete_or_archive_requests_are_safe_to_retry_or_fail_cleanly(): void
|
||||
{
|
||||
$failures = [];
|
||||
|
||||
foreach ($this->retryableDestructiveRoutes() as $route) {
|
||||
$uri = $route->uri();
|
||||
$first = $this->requestAs($this->actorFor($uri), 'DELETE', $uri);
|
||||
$second = $this->requestAs($this->actorFor($uri), 'DELETE', $uri);
|
||||
|
||||
foreach (['first' => $first, 'second' => $second] as $label => $response) {
|
||||
if ($response->getStatusCode() >= 500) {
|
||||
$failures[] = "DELETE $uri $label retry crashed: " . $response->getContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertSame([], $failures, "Retrying destructive operations must not crash:\n" . implode("\n", $failures));
|
||||
}
|
||||
|
||||
/** @return list<LaravelRoute> */
|
||||
private function duplicateSensitiveRoutes(): array
|
||||
{
|
||||
return array_slice(array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
|
||||
if ($this->primaryMethod($route) !== 'POST') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uri = $route->uri();
|
||||
|
||||
return str_contains($uri, 'payments')
|
||||
|| str_contains($uri, 'refund')
|
||||
|| str_contains($uri, 'invoices')
|
||||
|| str_contains($uri, 'attendance')
|
||||
|| str_contains($uri, 'messages')
|
||||
|| str_contains($uri, 'support')
|
||||
|| str_contains($uri, 'students')
|
||||
|| str_contains($uri, 'users')
|
||||
|| str_contains($uri, 'inventory')
|
||||
|| str_contains($uri, 'reimbursements');
|
||||
})), 0, 80);
|
||||
}
|
||||
|
||||
/** @return list<LaravelRoute> */
|
||||
private function retryableDestructiveRoutes(): array
|
||||
{
|
||||
return array_slice(array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
|
||||
$uri = $route->uri();
|
||||
|
||||
return $this->primaryMethod($route) === 'DELETE'
|
||||
|| str_contains($uri, 'archive')
|
||||
|| str_contains($uri, 'remove')
|
||||
|| str_contains($uri, 'delete');
|
||||
})), 0, 50);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user