using System.Net; using System.Net.Http.Json; using FluentAssertions; using ColaFlow.Modules.IssueManagement.Application.DTOs; using ColaFlow.Modules.IssueManagement.Domain.Enums; using ColaFlow.Modules.IssueManagement.IntegrationTests.Infrastructure; using Xunit; namespace ColaFlow.Modules.IssueManagement.IntegrationTests; /// /// Integration tests for Issue Management Module /// Tests all CRUD operations, status changes, assignments, and multi-tenant isolation /// public class IssueManagementIntegrationTests : IClassFixture { private readonly HttpClient _client; private readonly Guid _projectId = Guid.NewGuid(); private readonly Guid _tenantId = Guid.NewGuid(); private readonly Guid _userId = Guid.NewGuid(); private readonly string _tenantSlug = "test-tenant"; private readonly string _userEmail = "test@example.com"; public IssueManagementIntegrationTests(IssueManagementWebApplicationFactory factory) { _client = factory.CreateClient(); _client.AddAuthHeader(_userId, _tenantId, _tenantSlug, _userEmail, "Member"); } [Fact] public async Task CreateIssue_WithStoryType_ShouldReturnCreatedIssue() { // Arrange var request = new { Title = "Implement user authentication", Description = "Add JWT authentication to the API", Type = "Story", Priority = "High" }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var issue = await response.Content.ReadFromJsonAsync(); issue.Should().NotBeNull(); issue!.Title.Should().Be(request.Title); issue.Description.Should().Be(request.Description); issue.Type.Should().Be("Story"); issue.Priority.Should().Be("High"); issue.Status.Should().Be("Backlog"); issue.ProjectId.Should().Be(_projectId); issue.TenantId.Should().Be(_tenantId); issue.ReporterId.Should().Be(_userId); } [Fact] public async Task CreateIssue_WithTaskType_ShouldReturnCreatedIssue() { // Arrange var request = new { Title = "Write unit tests", Description = "Add unit tests for Issue Management module", Type = "Task", Priority = "Medium" }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var issue = await response.Content.ReadFromJsonAsync(); issue.Should().NotBeNull(); issue!.Type.Should().Be("Task"); issue.Priority.Should().Be("Medium"); } [Fact] public async Task CreateIssue_WithBugType_ShouldReturnCreatedIssue() { // Arrange var request = new { Title = "Fix login redirect issue", Description = "Users are not redirected after login", Type = "Bug", Priority = "Critical" }; // Act var response = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", request); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); var issue = await response.Content.ReadFromJsonAsync(); issue.Should().NotBeNull(); issue!.Type.Should().Be("Bug"); issue.Priority.Should().Be("Critical"); } [Fact] public async Task GetIssueById_WithExistingIssue_ShouldReturnIssue() { // Arrange - Create an issue first var createRequest = new { Title = "Test issue for retrieval", Description = "This issue will be retrieved by ID", Type = "Task", Priority = "Low" }; var createResponse = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", createRequest); var createdIssue = await createResponse.Content.ReadFromJsonAsync(); // Act var response = await _client.GetAsync($"/api/v1/projects/{_projectId}/issues/{createdIssue!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var issue = await response.Content.ReadFromJsonAsync(); issue.Should().NotBeNull(); issue!.Id.Should().Be(createdIssue.Id); issue.Title.Should().Be(createRequest.Title); } [Fact] public async Task ListIssues_WithMultipleIssues_ShouldReturnAllIssues() { // Arrange - Create multiple issues var issues = new[] { new { Title = "Issue 1", Description = "Description 1", Type = "Story", Priority = "High" }, new { Title = "Issue 2", Description = "Description 2", Type = "Task", Priority = "Medium" }, new { Title = "Issue 3", Description = "Description 3", Type = "Bug", Priority = "Low" } }; foreach (var issueRequest in issues) { await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", issueRequest); } // Act var response = await _client.GetAsync($"/api/v1/projects/{_projectId}/issues"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var retrievedIssues = await response.Content.ReadFromJsonAsync>(); retrievedIssues.Should().NotBeNull(); retrievedIssues!.Count.Should().BeGreaterThanOrEqualTo(3); } [Fact] public async Task ChangeIssueStatus_FromBacklogToInProgress_ShouldUpdateStatus() { // Arrange - Create an issue var createRequest = new { Title = "Issue for status change", Description = "This issue will have its status changed", Type = "Story", Priority = "High" }; var createResponse = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", createRequest); var createdIssue = await createResponse.Content.ReadFromJsonAsync(); // Act - Change status to InProgress var statusRequest = new { Status = "InProgress", OldStatus = "Backlog" }; var response = await _client.PutAsJsonAsync( $"/api/v1/projects/{_projectId}/issues/{createdIssue!.Id}/status", statusRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); // Verify status was changed var getResponse = await _client.GetAsync($"/api/v1/projects/{_projectId}/issues/{createdIssue.Id}"); var updatedIssue = await getResponse.Content.ReadFromJsonAsync(); updatedIssue.Should().NotBeNull(); updatedIssue!.Status.Should().Be("InProgress"); } [Fact] public async Task AssignIssue_ToUser_ShouldUpdateAssignee() { // Arrange - Create an issue var createRequest = new { Title = "Issue for assignment", Description = "This issue will be assigned to a user", Type = "Task", Priority = "Medium" }; var createResponse = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", createRequest); var createdIssue = await createResponse.Content.ReadFromJsonAsync(); var assigneeId = Guid.NewGuid(); // Act - Assign issue to user var assignRequest = new { AssigneeId = assigneeId }; var response = await _client.PutAsJsonAsync( $"/api/v1/projects/{_projectId}/issues/{createdIssue!.Id}/assign", assignRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); // Verify assignment var getResponse = await _client.GetAsync($"/api/v1/projects/{_projectId}/issues/{createdIssue.Id}"); var updatedIssue = await getResponse.Content.ReadFromJsonAsync(); updatedIssue.Should().NotBeNull(); updatedIssue!.AssigneeId.Should().Be(assigneeId); } [Fact] public async Task MultiTenantIsolation_DifferentTenant_ShouldNotAccessIssues() { // Arrange - Create an issue with tenant A var createRequest = new { Title = "Tenant A issue", Description = "This issue belongs to Tenant A", Type = "Story", Priority = "High" }; var createResponse = await _client.PostAsJsonAsync($"/api/v1/projects/{_projectId}/issues", createRequest); var createdIssue = await createResponse.Content.ReadFromJsonAsync(); // Act - Try to access with tenant B credentials var tenantBId = Guid.NewGuid(); var tenantBClient = _client; tenantBClient.DefaultRequestHeaders.Clear(); tenantBClient.AddAuthHeader(_userId, tenantBId, "tenant-b", _userEmail, "Member"); var response = await tenantBClient.GetAsync($"/api/v1/projects/{_projectId}/issues/{createdIssue!.Id}"); // Assert - Tenant B should not see Tenant A's issue // The issue will return NotFound or the global query filter will exclude it response.StatusCode.Should().Be(HttpStatusCode.NotFound); } }