action = new CreateBucketAction(); $this->scenario = Scenario::factory()->create(); } public function test_can_create_fixed_limit_bucket(): void { $bucket = $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_FIXED_LIMIT, 1000.00 ); $this->assertInstanceOf(Bucket::class, $bucket); $this->assertEquals('Test Bucket', $bucket->name); $this->assertEquals(Bucket::TYPE_FIXED_LIMIT, $bucket->allocation_type); $this->assertEquals(1000.00, $bucket->allocation_value); $this->assertEquals(1, $bucket->priority); $this->assertEquals(1, $bucket->sort_order); $this->assertEquals($this->scenario->id, $bucket->scenario_id); } public function test_can_create_percentage_bucket(): void { $bucket = $this->action->execute( $this->scenario, 'Percentage Bucket', Bucket::TYPE_PERCENTAGE, 25.5 ); $this->assertEquals(Bucket::TYPE_PERCENTAGE, $bucket->allocation_type); $this->assertEquals(25.5, $bucket->allocation_value); } public function test_can_create_unlimited_bucket(): void { $bucket = $this->action->execute( $this->scenario, 'Unlimited Bucket', Bucket::TYPE_UNLIMITED ); $this->assertEquals(Bucket::TYPE_UNLIMITED, $bucket->allocation_type); $this->assertNull($bucket->allocation_value); } public function test_unlimited_bucket_ignores_allocation_value(): void { $bucket = $this->action->execute( $this->scenario, 'Unlimited Bucket', Bucket::TYPE_UNLIMITED, 999.99 // This should be ignored and set to null ); $this->assertEquals(Bucket::TYPE_UNLIMITED, $bucket->allocation_type); $this->assertNull($bucket->allocation_value); } public function test_priority_auto_increments_when_not_specified(): void { $bucket1 = $this->action->execute( $this->scenario, 'First Bucket', Bucket::TYPE_FIXED_LIMIT, 100 ); $bucket2 = $this->action->execute( $this->scenario, 'Second Bucket', Bucket::TYPE_FIXED_LIMIT, 200 ); $this->assertEquals(1, $bucket1->priority); $this->assertEquals(2, $bucket2->priority); } public function test_can_specify_custom_priority(): void { $bucket = $this->action->execute( $this->scenario, 'Priority Bucket', Bucket::TYPE_FIXED_LIMIT, 100, 5 ); $this->assertEquals(5, $bucket->priority); } public function test_existing_priorities_are_shifted_when_inserting(): void { // Create initial buckets $bucket1 = $this->action->execute($this->scenario, 'Bucket 1', Bucket::TYPE_FIXED_LIMIT, 100, 1); $bucket2 = $this->action->execute($this->scenario, 'Bucket 2', Bucket::TYPE_FIXED_LIMIT, 200, 2); $bucket3 = $this->action->execute($this->scenario, 'Bucket 3', Bucket::TYPE_FIXED_LIMIT, 300, 3); // Insert a bucket at priority 2 $newBucket = $this->action->execute($this->scenario, 'New Bucket', Bucket::TYPE_FIXED_LIMIT, 150, 2); // Refresh models from database $bucket1->refresh(); $bucket2->refresh(); $bucket3->refresh(); // Check that priorities were shifted correctly $this->assertEquals(1, $bucket1->priority); $this->assertEquals(2, $newBucket->priority); $this->assertEquals(3, $bucket2->priority); // Shifted from 2 to 3 $this->assertEquals(4, $bucket3->priority); // Shifted from 3 to 4 } public function test_throws_exception_for_invalid_allocation_type(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid allocation type: invalid_type'); $this->action->execute( $this->scenario, 'Test Bucket', 'invalid_type', 100 ); } public function test_throws_exception_for_fixed_limit_without_allocation_value(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Fixed limit buckets require an allocation value'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_FIXED_LIMIT, null ); } public function test_throws_exception_for_negative_fixed_limit_value(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Fixed limit allocation value must be non-negative'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_FIXED_LIMIT, -100 ); } public function test_throws_exception_for_percentage_without_allocation_value(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Percentage buckets require an allocation value'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_PERCENTAGE, null ); } public function test_throws_exception_for_percentage_below_minimum(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Percentage allocation value must be between 0.01 and 100'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_PERCENTAGE, 0.005 ); } public function test_throws_exception_for_percentage_above_maximum(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Percentage allocation value must be between 0.01 and 100'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_PERCENTAGE, 101 ); } public function test_throws_exception_for_negative_priority(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Priority must be at least 1'); $this->action->execute( $this->scenario, 'Test Bucket', Bucket::TYPE_FIXED_LIMIT, 100, 0 ); } public function test_create_default_buckets(): void { $this->action->createDefaultBuckets($this->scenario); $buckets = $this->scenario->buckets()->orderBy('priority')->get(); $this->assertCount(3, $buckets); // Monthly Expenses $this->assertEquals('Monthly Expenses', $buckets[0]->name); $this->assertEquals(1, $buckets[0]->priority); $this->assertEquals(Bucket::TYPE_FIXED_LIMIT, $buckets[0]->allocation_type); $this->assertEquals(0, $buckets[0]->allocation_value); // Emergency Fund $this->assertEquals('Emergency Fund', $buckets[1]->name); $this->assertEquals(2, $buckets[1]->priority); $this->assertEquals(Bucket::TYPE_FIXED_LIMIT, $buckets[1]->allocation_type); $this->assertEquals(0, $buckets[1]->allocation_value); // Investments $this->assertEquals('Investments', $buckets[2]->name); $this->assertEquals(3, $buckets[2]->priority); $this->assertEquals(Bucket::TYPE_UNLIMITED, $buckets[2]->allocation_type); $this->assertNull($buckets[2]->allocation_value); } public function test_creates_buckets_in_database_transaction(): void { // This test ensures database consistency by creating multiple buckets // and verifying they all exist with correct priorities $this->action->execute($this->scenario, 'Bucket 1', Bucket::TYPE_FIXED_LIMIT, 100, 1); $this->action->execute($this->scenario, 'Bucket 2', Bucket::TYPE_FIXED_LIMIT, 200, 1); // Insert at priority 1 // Both buckets should exist with correct priorities $buckets = $this->scenario->buckets()->orderBy('priority')->get(); $this->assertCount(2, $buckets); $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 } }