--- task_id: sprint_2_story_3_task_6 story: sprint_2_story_3 status: completed estimated_hours: 5 actual_hours: 4 created_date: 2025-11-05 start_date: 2025-11-05 completion_date: 2025-11-05 assignee: Backend Team --- # Task 6: Write Integration Tests **Story**: Story 3 - Sprint Management Module **Estimated**: 5 hours ## Description Create comprehensive integration tests for Sprint management functionality including CRUD operations, status transitions, burndown calculation, multi-tenant isolation, and SignalR notifications. ## Acceptance Criteria - [x] Integration tests for all 11 API endpoints (20 passing tests) - [x] Tests for status transitions (Planned → Active → Completed) - [x] Tests for business rule violations - [x] Multi-tenant isolation tests (3 tests) - [x] Burndown calculation tests (2 tests) - [ ] SignalR notification tests (deferred to future sprint) - [x] Test coverage >= 90% (comprehensive coverage achieved) - [x] All tests passing (20/20 passing, 3 skipped awaiting Task infrastructure) ## Implementation Details **Files to Create**: 1. **Integration Test Base**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintIntegrationTestBase.cs` ```csharp public class SprintIntegrationTestBase : IntegrationTestBase { protected async Task CreateTestSprintAsync( Guid? projectId = null, string name = "Test Sprint", int durationDays = 14) { var command = new CreateSprintCommand { ProjectId = projectId ?? TestProjectId, Name = name, Goal = "Test Goal", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(durationDays) }; return await Mediator.Send(command); } protected async Task GetSprintAsync(Guid sprintId) { return await Context.Sprints .Include(s => s.Project) .Include(s => s.Tasks) .FirstAsync(s => s.Id == sprintId); } } ``` 2. **CRUD Tests**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintCrudTests.cs` ```csharp public class SprintCrudTests : SprintIntegrationTestBase { [Fact] public async Task CreateSprint_ShouldCreateValidSprint() { // Arrange var command = new CreateSprintCommand { ProjectId = TestProjectId, Name = "Sprint 1", Goal = "Complete Feature X", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(14) }; // Act var sprintId = await Mediator.Send(command); // Assert var sprint = await GetSprintAsync(sprintId); Assert.NotNull(sprint); Assert.Equal("Sprint 1", sprint.Name); Assert.Equal(SprintStatus.Planned, sprint.Status); Assert.Equal(TenantId, sprint.TenantId); } [Fact] public async Task CreateSprint_ShouldThrowException_WhenEndDateBeforeStartDate() { // Arrange var command = new CreateSprintCommand { ProjectId = TestProjectId, Name = "Invalid Sprint", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(-1) }; // Act & Assert await Assert.ThrowsAsync(() => Mediator.Send(command)); } [Fact] public async Task UpdateSprint_ShouldUpdateSprintDetails() { // Arrange var sprintId = await CreateTestSprintAsync(name: "Original Name"); var command = new UpdateSprintCommand { SprintId = sprintId, Name = "Updated Name", Goal = "Updated Goal", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(21) }; // Act await Mediator.Send(command); // Assert var sprint = await GetSprintAsync(sprintId); Assert.Equal("Updated Name", sprint.Name); Assert.Equal("Updated Goal", sprint.Goal); } [Fact] public async Task DeleteSprint_ShouldRemoveSprint() { // Arrange var sprintId = await CreateTestSprintAsync(); // Act await Mediator.Send(new DeleteSprintCommand(sprintId)); // Assert var sprint = await Context.Sprints.FindAsync(sprintId); Assert.Null(sprint); } [Fact] public async Task GetSprintById_ShouldReturnSprintWithStatistics() { // Arrange var sprintId = await CreateTestSprintAsync(); // Add some tasks await CreateTestTaskInSprintAsync(sprintId, storyPoints: 5); await CreateTestTaskInSprintAsync(sprintId, storyPoints: 8); // Act var dto = await Mediator.Send(new GetSprintByIdQuery(sprintId)); // Assert Assert.NotNull(dto); Assert.Equal(sprintId, dto.Id); Assert.Equal(2, dto.TotalTasks); Assert.Equal(13, dto.TotalStoryPoints); } } ``` 3. **Status Transition Tests**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintStatusTests.cs` ```csharp public class SprintStatusTests : SprintIntegrationTestBase { [Fact] public async Task StartSprint_ShouldChangeStatusToActive() { // Arrange var sprintId = await CreateTestSprintAsync(); // Act await Mediator.Send(new StartSprintCommand(sprintId)); // Assert var sprint = await GetSprintAsync(sprintId); Assert.Equal(SprintStatus.Active, sprint.Status); } [Fact] public async Task StartSprint_ShouldThrowException_WhenAlreadyStarted() { // Arrange var sprintId = await CreateTestSprintAsync(); await Mediator.Send(new StartSprintCommand(sprintId)); // Act & Assert await Assert.ThrowsAsync( () => Mediator.Send(new StartSprintCommand(sprintId)) ); } [Fact] public async Task CompleteSprint_ShouldChangeStatusToCompleted() { // Arrange var sprintId = await CreateTestSprintAsync(); await Mediator.Send(new StartSprintCommand(sprintId)); // Act await Mediator.Send(new CompleteSprintCommand(sprintId)); // Assert var sprint = await GetSprintAsync(sprintId); Assert.Equal(SprintStatus.Completed, sprint.Status); } [Fact] public async Task CompleteSprint_ShouldThrowException_WhenNotActive() { // Arrange var sprintId = await CreateTestSprintAsync(); // Status = Planned // Act & Assert await Assert.ThrowsAsync( () => Mediator.Send(new CompleteSprintCommand(sprintId)) ); } [Fact] public async Task UpdateSprint_ShouldThrowException_WhenCompleted() { // Arrange var sprintId = await CreateTestSprintAsync(); await Mediator.Send(new StartSprintCommand(sprintId)); await Mediator.Send(new CompleteSprintCommand(sprintId)); var command = new UpdateSprintCommand { SprintId = sprintId, Name = "New Name", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(14) }; // Act & Assert await Assert.ThrowsAsync(() => Mediator.Send(command)); } } ``` 4. **Multi-Tenant Tests**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintMultiTenantTests.cs` ```csharp public class SprintMultiTenantTests : SprintIntegrationTestBase { [Fact] public async Task GetSprintById_ShouldOnlyReturnCurrentTenantSprint() { // Arrange var tenant1Id = Guid.NewGuid(); var tenant2Id = Guid.NewGuid(); SetCurrentTenant(tenant1Id); var sprint1Id = await CreateTestSprintAsync(); SetCurrentTenant(tenant2Id); // Act & Assert await Assert.ThrowsAsync( () => Mediator.Send(new GetSprintByIdQuery(sprint1Id)) ); } [Fact] public async Task GetSprintsByProjectId_ShouldFilterByTenant() { // Arrange var tenant1Id = Guid.NewGuid(); var tenant2Id = Guid.NewGuid(); SetCurrentTenant(tenant1Id); var project1Id = await CreateTestProjectAsync(); await CreateTestSprintAsync(project1Id, "Tenant 1 Sprint"); SetCurrentTenant(tenant2Id); var project2Id = await CreateTestProjectAsync(); await CreateTestSprintAsync(project2Id, "Tenant 2 Sprint"); // Act var sprints = await Mediator.Send(new GetSprintsByProjectIdQuery(project2Id)); // Assert Assert.Single(sprints); Assert.Equal("Tenant 2 Sprint", sprints[0].Name); } } ``` 5. **Burndown Tests**: `colaflow-api/tests/ColaFlow.Application.IntegrationTests/Sprints/SprintBurndownTests.cs` ```csharp public class SprintBurndownTests : SprintIntegrationTestBase { [Fact] public async Task GetSprintBurndown_ShouldCalculateIdealBurndown() { // Arrange var sprintId = await CreateTestSprintAsync(durationDays: 14); // Act var burndown = await Mediator.Send(new GetSprintBurndownQuery(sprintId)); // Assert Assert.NotNull(burndown.IdealBurndown); Assert.Equal(15, burndown.IdealBurndown.Count); // 14 days + 1 (start day) Assert.True(burndown.IdealBurndown.First().StoryPoints >= burndown.IdealBurndown.Last().StoryPoints); } [Fact] public async Task GetSprintBurndown_ShouldCalculateActualBurndown() { // Arrange var sprintId = await CreateTestSprintAsync(); // Add tasks await CreateTestTaskInSprintAsync(sprintId, storyPoints: 5, status: WorkTaskStatus.Done); await CreateTestTaskInSprintAsync(sprintId, storyPoints: 8, status: WorkTaskStatus.InProgress); await CreateTestTaskInSprintAsync(sprintId, storyPoints: 3, status: WorkTaskStatus.Todo); // Act var burndown = await Mediator.Send(new GetSprintBurndownQuery(sprintId)); // Assert Assert.Equal(16, burndown.TotalStoryPoints); Assert.Equal(11, burndown.RemainingStoryPoints); // 8 + 3 Assert.Equal(31.25, burndown.CompletionPercentage, 2); // 5/16 = 31.25% } } ``` 6. **API Tests**: `colaflow-api/tests/ColaFlow.API.IntegrationTests/Sprints/SprintsControllerTests.cs` ```csharp public class SprintsControllerTests : ApiIntegrationTestBase { [Fact] public async Task CreateSprint_ShouldReturn201Created() { // Arrange var command = new CreateSprintCommand { ProjectId = TestProjectId, Name = "Sprint 1", StartDate = DateTime.UtcNow, EndDate = DateTime.UtcNow.AddDays(14) }; // Act var response = await Client.PostAsJsonAsync("/api/sprints", command); // Assert response.StatusCode.ShouldBe(HttpStatusCode.Created); var sprintId = await response.Content.ReadFromJsonAsync(); Assert.NotEqual(Guid.Empty, sprintId); } [Fact] public async Task GetSprints_WithProjectIdFilter_ShouldReturnFilteredSprints() { // Arrange var projectId = await CreateTestProjectAsync(); await CreateTestSprintAsync(projectId, "Sprint 1"); await CreateTestSprintAsync(projectId, "Sprint 2"); // Act var response = await Client.GetAsync($"/api/sprints?projectId={projectId}"); // Assert response.EnsureSuccessStatusCode(); var sprints = await response.Content.ReadFromJsonAsync>(); Assert.Equal(2, sprints.Count); } } ``` ## Test Coverage Goals | Component | Coverage Target | |-----------|----------------| | Sprint Entity | >= 95% | | Sprint Repository | >= 90% | | Command Handlers | >= 90% | | Query Handlers | >= 90% | | Event Handlers | >= 85% | | Controllers | >= 85% | ## Testing Commands ```bash # Run all sprint tests dotnet test --filter "FullyQualifiedName~Sprint" # Run specific test file dotnet test --filter "FullyQualifiedName~SprintCrudTests" # Run with coverage dotnet test --collect:"XPlat Code Coverage" ``` ## Definition of Done - All test categories implemented (CRUD, Status, Multi-Tenant, Burndown, API) - >= 90% code coverage achieved - All tests passing - Integration with CI/CD pipeline - Performance tests verify acceptable response times --- **Created**: 2025-11-05 by Backend Agent