create([ 'email' => 'manager@example.com', 'password' => bcrypt('password123'), 'role' => 'manager', 'active' => true, ]); $response = $this->postJson('/api/auth/login', [ 'email' => 'manager@example.com', 'password' => 'password123', ]); return $response->json('access_token'); } // 5.1.11 API test: POST /api/allocations creates allocation public function test_post_allocations_creates_allocation() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02', 'allocated_hours' => 40, ]); $response->assertStatus(201); $response->assertJson([ 'data' => [ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'allocated_hours' => '40.00', ], ]); $this->assertDatabaseHas('allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'allocated_hours' => 40, ]); } // 5.1.12 API test: Validate hours >= 0 public function test_validate_hours_must_be_greater_than_or_equal_zero() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id]); $project = Project::factory()->create(); // Test with negative hours $response = $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02', 'allocated_hours' => -10, ]); $response->assertStatus(422); $response->assertJsonValidationErrors(['allocated_hours']); $response->assertJsonFragment([ 'allocated_hours' => ['The allocated hours field must be at least 0.'], ]); } // 5.1.13 API test: GET /api/allocations returns matrix public function test_get_allocations_returns_matrix() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(); // Create allocation Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02', 'allocated_hours' => 40, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonStructure([ 'data' => [ '*' => [ 'project_id', 'team_member_id', 'month', 'allocated_hours', ], ], ]); } // 5.1.14 API test: PUT /api/allocations/{id} updates public function test_put_allocations_updates() { $token = $this->loginAsManager(); $allocation = Allocation::factory()->create([ 'allocated_hours' => 40, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->putJson("/api/allocations/{$allocation->id}", [ 'allocated_hours' => 60, ]); $response->assertStatus(200); $response->assertJson([ 'data' => [ 'id' => $allocation->id, 'allocated_hours' => '60.00', ], ]); $this->assertDatabaseHas('allocations', [ 'id' => $allocation->id, 'allocated_hours' => 60, ]); } // 5.1.15 API test: DELETE /api/allocations/{id} removes public function test_delete_allocation_removes() { $token = $this->loginAsManager(); $allocation = Allocation::factory()->create(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson("/api/allocations/{$allocation->id}"); $response->assertStatus(200); $response->assertJson([ 'message' => 'Allocation deleted successfully', ]); $this->assertDatabaseMissing('allocations', [ 'id' => $allocation->id, ]); } // 5.1.16 API test: POST /api/allocations/bulk creates multiple public function test_post_allocations_bulk_creates_multiple() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember1 = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $teamMember2 = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/allocations/bulk', [ 'allocations' => [ [ 'project_id' => $project->id, 'team_member_id' => $teamMember1->id, 'month' => '2026-02', 'allocated_hours' => 40, ], [ 'project_id' => $project->id, 'team_member_id' => $teamMember2->id, 'month' => '2026-02', 'allocated_hours' => 32, ], ], ]); $response->assertStatus(201); $response->assertJsonCount(2, 'data'); $this->assertDatabaseHas('allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember1->id, 'allocated_hours' => 40, ]); $this->assertDatabaseHas('allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember2->id, 'allocated_hours' => 32, ]); } // Test: Allocate zero hours is allowed public function test_allocate_zero_hours_is_allowed() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id]); $project = Project::factory()->create(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/allocations', [ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02', 'allocated_hours' => 0, ]); $response->assertStatus(201); $response->assertJson([ 'data' => [ 'allocated_hours' => '0.00', ], ]); } // Test: Cannot update non-existent allocation public function test_cannot_update_nonexistent_allocation() { $token = $this->loginAsManager(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->putJson('/api/allocations/nonexistent-id', [ 'allocated_hours' => 60, ]); $response->assertStatus(404); $response->assertJson([ 'message' => 'Allocation not found', ]); } // Test: Cannot delete non-existent allocation public function test_cannot_delete_nonexistent_allocation() { $token = $this->loginAsManager(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson('/api/allocations/nonexistent-id'); $response->assertStatus(404); } // ===== Allocation Indicator Tests ===== // 1.1 API test: GET /api/allocations returns allocation_indicator per item public function test_get_allocations_returns_allocation_indicator() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(['approved_estimate' => 100]); Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02-01', 'allocated_hours' => 100, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonStructure([ 'data' => [ '*' => ['allocation_indicator'], ], ]); } // 1.2 API test: allocation_indicator is green when >= 100% public function test_allocation_indicator_is_green_when_full() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(['approved_estimate' => 100]); Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02-01', 'allocated_hours' => 100, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonPath('data.0.allocation_indicator', 'green'); } // 1.3 API test: allocation_indicator is yellow when < 100% public function test_allocation_indicator_is_yellow_when_under() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(['approved_estimate' => 100]); Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02-01', 'allocated_hours' => 80, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonPath('data.0.allocation_indicator', 'yellow'); } // 1.4 API test: allocation_indicator is red when > 100% public function test_allocation_indicator_is_red_when_over() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(['approved_estimate' => 100]); Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02-01', 'allocated_hours' => 120, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonPath('data.0.allocation_indicator', 'red'); } // 1.5 API test: allocation_indicator is gray when no approved_estimate public function test_allocation_indicator_is_gray_when_no_estimate() { $token = $this->loginAsManager(); $role = Role::factory()->create(); $teamMember = TeamMember::factory()->create(['role_id' => $role->id, 'active' => true]); $project = Project::factory()->create(['approved_estimate' => null]); Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => $teamMember->id, 'month' => '2026-02-01', 'allocated_hours' => 40, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonPath('data.0.allocation_indicator', 'gray'); } // ===== Untracked Allocation Tests ===== // 2.1 API test: POST /api/allocations accepts null team_member_id public function test_can_create_allocation_with_null_team_member() { $token = $this->loginAsManager(); $project = Project::factory()->create(); $response = $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/allocations', [ 'project_id' => $project->id, 'team_member_id' => null, 'month' => '2026-02', 'allocated_hours' => 40, ]); $response->assertStatus(201); $response->assertJsonPath('data.team_member_id', null); $response->assertJsonPath('data.team_member', null); } // 2.2 API test: GET /api/allocations returns null team_member public function test_get_allocations_returns_null_team_member() { $token = $this->loginAsManager(); $project = Project::factory()->create(); // Create untracked allocation Allocation::factory()->create([ 'project_id' => $project->id, 'team_member_id' => null, 'month' => '2026-02-01', 'allocated_hours' => 40, ]); $response = $this->withHeader('Authorization', "Bearer {$token}") ->getJson('/api/allocations?month=2026-02'); $response->assertStatus(200); $response->assertJsonPath('data.0.team_member_id', null); $response->assertJsonPath('data.0.team_member', null); } }