diff --git a/app/Actions/CreateBucketAction.php b/app/Actions/CreateBucketAction.php index e19771f..7e0c0c0 100644 --- a/app/Actions/CreateBucketAction.php +++ b/app/Actions/CreateBucketAction.php @@ -18,6 +18,7 @@ public function execute( BucketTypeEnum $type = BucketTypeEnum::NEED, ?float $allocationValue = null, ?int $priority = null, + ?float $bufferMultiplier = null, ): Bucket { // Validate type + allocation type constraints $this->validateTypeConstraints($type, $allocationType); @@ -30,12 +31,23 @@ public function execute( // Validate allocation value based on type $this->validateAllocationValue($allocationType, $allocationValue); + // Validate and normalize buffer multiplier + $bufferMultiplier = $bufferMultiplier ?? 0.0; + if ($bufferMultiplier < 0) { + throw new InvalidArgumentException('Buffer multiplier must be non-negative'); + } + // Set allocation_value to null for unlimited buckets if ($allocationType === BucketAllocationTypeEnum::UNLIMITED) { $allocationValue = null; } - return DB::transaction(function () use ($scenario, $name, $allocationType, $allocationValue, $priority, $type) { + // Buffer only applies to fixed_limit buckets + if ($allocationType !== BucketAllocationTypeEnum::FIXED_LIMIT) { + $bufferMultiplier = 0.0; + } + + return DB::transaction(function () use ($scenario, $name, $allocationType, $allocationValue, $priority, $type, $bufferMultiplier) { // Determine priority (append to end if not specified) if ($priority === null) { $maxPriority = $scenario->buckets()->max('priority') ?? 0; @@ -68,6 +80,7 @@ public function execute( 'sort_order' => $priority, // Start with sort_order matching priority 'allocation_type' => $allocationType, 'allocation_value' => $allocationValue, + 'buffer_multiplier' => $bufferMultiplier, ]); }); } diff --git a/tests/Unit/Actions/CreateBucketActionTest.php b/tests/Unit/Actions/CreateBucketActionTest.php index aac2d0c..1102032 100644 --- a/tests/Unit/Actions/CreateBucketActionTest.php +++ b/tests/Unit/Actions/CreateBucketActionTest.php @@ -359,4 +359,69 @@ public function test_creates_buckets_in_database_transaction(): void $this->assertEquals('Bucket 2', $buckets[0]->name); // New bucket at priority 1 $this->assertEquals('Bucket 1', $buckets[1]->name); // Original bucket shifted to priority 2 } + + public function test_can_create_fixed_limit_bucket_with_buffer_multiplier(): void + { + $bucket = $this->action->execute( + $this->scenario, + 'Buffered Bucket', + BucketAllocationTypeEnum::FIXED_LIMIT, + allocationValue: 50000, + bufferMultiplier: 1.5, + ); + + $this->assertEquals('1.50', $bucket->buffer_multiplier); + } + + public function test_buffer_multiplier_defaults_to_zero(): void + { + $bucket = $this->action->execute( + $this->scenario, + 'No Buffer Bucket', + BucketAllocationTypeEnum::FIXED_LIMIT, + allocationValue: 50000, + ); + + $this->assertEquals('0.00', $bucket->buffer_multiplier); + } + + public function test_buffer_multiplier_forced_to_zero_for_percentage_bucket(): void + { + $bucket = $this->action->execute( + $this->scenario, + 'Percentage Bucket', + BucketAllocationTypeEnum::PERCENTAGE, + allocationValue: 25.0, + bufferMultiplier: 1.0, + ); + + $this->assertEquals('0.00', $bucket->buffer_multiplier); + } + + public function test_buffer_multiplier_forced_to_zero_for_unlimited_bucket(): void + { + $bucket = $this->action->execute( + $this->scenario, + 'Unlimited Bucket', + BucketAllocationTypeEnum::UNLIMITED, + BucketTypeEnum::OVERFLOW, + bufferMultiplier: 1.0, + ); + + $this->assertEquals('0.00', $bucket->buffer_multiplier); + } + + public function test_throws_exception_for_negative_buffer_multiplier(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Buffer multiplier must be non-negative'); + + $this->action->execute( + $this->scenario, + 'Bad Buffer', + BucketAllocationTypeEnum::FIXED_LIMIT, + allocationValue: 50000, + bufferMultiplier: -0.5, + ); + } }