handle($request, fn () => response('ok')); $this->assertSame(400, $response->getStatusCode()); $this->assertStringContainsString('idempotency_key_required', (string) $response->getContent()); } public function test_first_request_caches_and_passes_through(): void { Cache::flush(); $middleware = new IdempotencyKey60sRedis; $request = Request::create('/x', 'POST'); $request->headers->set('Idempotency-Key', 'abc-123'); $count = 0; $response = $middleware->handle($request, function () use (&$count): Response { $count++; return response()->json(['ok' => true]); }); $this->assertSame(200, $response->getStatusCode()); $this->assertSame(1, $count); } public function test_replayed_request_returns_cached_body_with_replayed_header(): void { Cache::flush(); $middleware = new IdempotencyKey60sRedis; $request1 = Request::create('/x', 'POST'); $request1->headers->set('Idempotency-Key', 'replay-key'); $count = 0; $middleware->handle($request1, function () use (&$count) { $count++; return response()->json(['result' => 'one']); }); $request2 = Request::create('/x', 'POST'); $request2->headers->set('Idempotency-Key', 'replay-key'); $response2 = $middleware->handle($request2, function () use (&$count) { $count++; return response()->json(['result' => 'two']); }); $this->assertSame(1, $count, 'inner handler should not run on replay'); $this->assertSame('true', $response2->headers->get('Idempotency-Replayed')); $this->assertStringContainsString('one', (string) $response2->getContent()); } public function test_failed_response_not_cached(): void { Cache::flush(); $middleware = new IdempotencyKey60sRedis; $request1 = Request::create('/x', 'POST'); $request1->headers->set('Idempotency-Key', 'fail-key'); $middleware->handle($request1, fn () => response()->json(['x' => 1], 422)); $request2 = Request::create('/x', 'POST'); $request2->headers->set('Idempotency-Key', 'fail-key'); $count = 0; $middleware->handle($request2, function () use (&$count) { $count++; return response()->json(['x' => 2]); }); $this->assertSame(1, $count, 'failed responses should not be cached for replay'); } }