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 */ 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 */ 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); } }