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);
}
}