4 - Harden overflow bucket invariants with server-side guards
This commit is contained in:
parent
faff18f82b
commit
d06b859652
4 changed files with 41 additions and 1 deletions
|
|
@ -22,6 +22,11 @@ public function execute(
|
||||||
// Validate type + allocation type constraints
|
// Validate type + allocation type constraints
|
||||||
$this->validateTypeConstraints($type, $allocationType);
|
$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
|
// Validate allocation value based on type
|
||||||
$this->validateAllocationValue($allocationType, $allocationValue);
|
$this->validateAllocationValue($allocationType, $allocationValue);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,15 @@ public function update(Request $request, Bucket $bucket): JsonResponse
|
||||||
$type = BucketTypeEnum::from($validated['type']);
|
$type = BucketTypeEnum::from($validated['type']);
|
||||||
$allocationType = BucketAllocationTypeEnum::from($validated['allocation_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);
|
$constraintError = $this->validateBucketTypeConstraints($type, $allocationType, $bucket->scenario, $bucket);
|
||||||
if ($constraintError) {
|
if ($constraintError) {
|
||||||
return $constraintError;
|
return $constraintError;
|
||||||
|
|
@ -115,6 +124,12 @@ public function update(Request $request, Bucket $bucket): JsonResponse
|
||||||
*/
|
*/
|
||||||
public function destroy(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;
|
$scenarioId = $bucket->scenario_id;
|
||||||
$deletedPriority = $bucket->priority;
|
$deletedPriority = $bucket->priority;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ class BucketFactory extends Factory
|
||||||
{
|
{
|
||||||
public function definition(): array
|
public function definition(): array
|
||||||
{
|
{
|
||||||
|
// Unlimited excluded — use ->overflow() state modifier
|
||||||
$allocationType = $this->faker->randomElement([
|
$allocationType = $this->faker->randomElement([
|
||||||
BucketAllocationTypeEnum::FIXED_LIMIT,
|
BucketAllocationTypeEnum::FIXED_LIMIT,
|
||||||
BucketAllocationTypeEnum::PERCENTAGE,
|
BucketAllocationTypeEnum::PERCENTAGE,
|
||||||
BucketAllocationTypeEnum::UNLIMITED,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -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
|
public function test_want_bucket_cannot_use_unlimited_allocation(): void
|
||||||
{
|
{
|
||||||
$this->expectException(InvalidArgumentException::class);
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue