diff --git a/app/Actions/CreateBucketAction.php b/app/Actions/CreateBucketAction.php index b3bf2b9..0397bb7 100644 --- a/app/Actions/CreateBucketAction.php +++ b/app/Actions/CreateBucketAction.php @@ -22,6 +22,11 @@ public function execute( // Validate type + allocation type constraints $this->validateTypeConstraints($type, $allocationType); + // Enforce one overflow bucket per scenario + if ($type === BucketTypeEnum::OVERFLOW && $scenario->buckets()->where('type', BucketTypeEnum::OVERFLOW->value)->exists()) { + throw new InvalidArgumentException('A scenario can only have one overflow bucket'); + } + // Validate allocation value based on type $this->validateAllocationValue($allocationType, $allocationValue); diff --git a/app/Http/Controllers/BucketController.php b/app/Http/Controllers/BucketController.php index 3641f86..19c2b45 100644 --- a/app/Http/Controllers/BucketController.php +++ b/app/Http/Controllers/BucketController.php @@ -80,6 +80,15 @@ public function update(Request $request, Bucket $bucket): JsonResponse $type = BucketTypeEnum::from($validated['type']); $allocationType = BucketAllocationTypeEnum::from($validated['allocation_type']); + // Prevent changing overflow bucket's type away from overflow + // (changing TO overflow is handled by validateBucketTypeConstraints below) + if ($bucket->type === BucketTypeEnum::OVERFLOW && $type !== BucketTypeEnum::OVERFLOW) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => ['type' => ['The overflow bucket\'s type cannot be changed.']], + ], 422); + } + $constraintError = $this->validateBucketTypeConstraints($type, $allocationType, $bucket->scenario, $bucket); if ($constraintError) { return $constraintError; @@ -115,6 +124,12 @@ public function update(Request $request, Bucket $bucket): JsonResponse */ public function destroy(Bucket $bucket): JsonResponse { + if ($bucket->type === BucketTypeEnum::OVERFLOW) { + return response()->json([ + 'message' => 'The overflow bucket cannot be deleted.', + ], 422); + } + $scenarioId = $bucket->scenario_id; $deletedPriority = $bucket->priority; diff --git a/database/factories/BucketFactory.php b/database/factories/BucketFactory.php index 93821ab..f7dada7 100644 --- a/database/factories/BucketFactory.php +++ b/database/factories/BucketFactory.php @@ -15,10 +15,10 @@ class BucketFactory extends Factory { public function definition(): array { + // Unlimited excluded — use ->overflow() state modifier $allocationType = $this->faker->randomElement([ BucketAllocationTypeEnum::FIXED_LIMIT, BucketAllocationTypeEnum::PERCENTAGE, - BucketAllocationTypeEnum::UNLIMITED, ]); return [ diff --git a/tests/Unit/Actions/CreateBucketActionTest.php b/tests/Unit/Actions/CreateBucketActionTest.php index b1567b1..2c86fa5 100644 --- a/tests/Unit/Actions/CreateBucketActionTest.php +++ b/tests/Unit/Actions/CreateBucketActionTest.php @@ -151,6 +151,26 @@ public function test_non_overflow_bucket_cannot_use_unlimited_allocation(): void ); } + public function test_cannot_create_second_overflow_bucket(): void + { + $this->action->execute( + $this->scenario, + 'First Overflow', + BucketAllocationTypeEnum::UNLIMITED, + BucketTypeEnum::OVERFLOW + ); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('A scenario can only have one overflow bucket'); + + $this->action->execute( + $this->scenario, + 'Second Overflow', + BucketAllocationTypeEnum::UNLIMITED, + BucketTypeEnum::OVERFLOW + ); + } + public function test_want_bucket_cannot_use_unlimited_allocation(): void { $this->expectException(InvalidArgumentException::class);