withCapacity(1)->create(); $hold = Hold::factory()->for($slot)->create(); $this->deleteJson("/holds/{$hold->id}") ->assertOk() ->assertJsonPath('status', HoldStatus::Cancelled->value) ->assertJsonPath('cancelled_at', fn (?string $value): bool => $value !== null); // Место не списывалось, remaining меняться не должен. $this->assertSame(1, $slot->refresh()->remaining); // Освободившееся место снова можно забронировать. $this->getJson('/slots/availability')->assertJsonPath('0.remaining', 1); $this->postJson("/slots/{$slot->id}/hold", [], ['Idempotency-Key' => (string) Str::uuid()]) ->assertCreated(); } public function test_cancelling_a_confirmed_hold_gives_the_seat_back(): void { $slot = Slot::factory()->withCapacity(1)->create(); $holdId = $this->postJson("/slots/{$slot->id}/hold", [], ['Idempotency-Key' => (string) Str::uuid()]) ->assertCreated() ->json('hold_id'); $this->postJson("/holds/{$holdId}/confirm")->assertOk(); $this->assertSame(0, $slot->refresh()->remaining); $this->getJson('/slots/availability')->assertJsonPath('0.remaining', 0); $this->deleteJson("/holds/{$holdId}")->assertOk(); $this->assertSame(1, $slot->refresh()->remaining); $this->getJson('/slots/availability')->assertJsonPath('0.remaining', 1); } public function test_cancelling_twice_is_idempotent(): void { $slot = Slot::factory()->withCapacity(2)->create(); $holdId = $this->postJson("/slots/{$slot->id}/hold", [], ['Idempotency-Key' => (string) Str::uuid()]) ->assertCreated() ->json('hold_id'); $this->postJson("/holds/{$holdId}/confirm")->assertOk(); $this->assertSame(1, $slot->refresh()->remaining); $this->deleteJson("/holds/{$holdId}")->assertOk(); $this->deleteJson("/holds/{$holdId}")->assertOk(); // Место возвращается ровно один раз. $this->assertSame(2, $slot->refresh()->remaining); } public function test_it_returns_404_for_an_unknown_hold(): void { $this->deleteJson('/holds/999')->assertNotFound(); } }