373 lines
13 KiB
C#
373 lines
13 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using Xunit;
|
|
using FluentAssertions;
|
|
using IamService.API.Application.Common;
|
|
using IamService.API.Controllers;
|
|
|
|
namespace IamService.FunctionalTests.Controllers;
|
|
|
|
/// <summary>
|
|
/// EN: Functional tests for OrganizationsController endpoints.
|
|
/// VI: Functional tests cho các endpoints của OrganizationsController.
|
|
/// </summary>
|
|
public class OrganizationsControllerTests : IClassFixture<CustomWebApplicationFactory>
|
|
{
|
|
private readonly HttpClient _client;
|
|
private readonly CustomWebApplicationFactory _factory;
|
|
|
|
public OrganizationsControllerTests(CustomWebApplicationFactory factory)
|
|
{
|
|
_factory = factory;
|
|
_client = factory.CreateClient();
|
|
|
|
// EN: Add authorization header with test token
|
|
// VI: Thêm authorization header với test token
|
|
_client.DefaultRequestHeaders.Authorization =
|
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", GenerateTestToken());
|
|
}
|
|
|
|
#region Create Organization Tests
|
|
|
|
[Fact]
|
|
public async Task CreateOrganization_ValidRequest_Returns201WithLocation()
|
|
{
|
|
// Arrange
|
|
var request = new CreateOrganizationRequest
|
|
{
|
|
Name = $"Test Organization {Guid.NewGuid():N}",
|
|
Slug = $"test-org-{Guid.NewGuid():N}".Substring(0, 30),
|
|
Description = "A test organization"
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/v1/organizations", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
response.Headers.Location.Should().NotBeNull();
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result.Should().NotBeNull();
|
|
result!.Success.Should().BeTrue();
|
|
result.Data.Should().NotBeNull();
|
|
result.Data!.Name.Should().Be(request.Name);
|
|
result.Data.Slug.Should().Be(request.Slug);
|
|
result.Data.Status.Should().Be("Active");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateOrganization_DuplicateSlug_Returns409Conflict()
|
|
{
|
|
// Arrange
|
|
var uniqueSlug = $"duplicate-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var request = new CreateOrganizationRequest
|
|
{
|
|
Name = "Organization 1",
|
|
Slug = uniqueSlug
|
|
};
|
|
|
|
// First creation - should succeed
|
|
var firstResponse = await _client.PostAsJsonAsync("/api/v1/organizations", request);
|
|
firstResponse.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
|
|
// Second creation with same slug - should fail
|
|
request.Name = "Organization 2"; // Different name, same slug
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/v1/organizations", request);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result.Should().NotBeNull();
|
|
result!.Success.Should().BeFalse();
|
|
result.Error.Should().NotBeNull();
|
|
result.Error!.Code.Should().Be("SLUG_EXISTS");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateOrganization_WithParentOrganization_Returns201WithParentId()
|
|
{
|
|
// Arrange - Create parent first
|
|
var parentSlug = $"parent-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var parentRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Parent Organization",
|
|
Slug = parentSlug
|
|
};
|
|
var parentResponse = await _client.PostAsJsonAsync("/api/v1/organizations", parentRequest);
|
|
var parentResult = await parentResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var parentId = parentResult!.Data!.Id;
|
|
|
|
// Create child
|
|
var childSlug = $"child-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var childRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Child Organization",
|
|
Slug = childSlug,
|
|
ParentOrganizationId = parentId
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PostAsJsonAsync("/api/v1/organizations", childRequest);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result!.Data!.ParentOrganizationId.Should().Be(parentId);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Get Organization Tests
|
|
|
|
[Fact]
|
|
public async Task GetOrganizationById_ExistingId_Returns200WithData()
|
|
{
|
|
// Arrange - Create organization first
|
|
var slug = $"get-test-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Get Test Organization",
|
|
Slug = slug
|
|
};
|
|
var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var orgId = createResult!.Data!.Id;
|
|
|
|
// Act
|
|
var response = await _client.GetAsync($"/api/v1/organizations/{orgId}");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result.Should().NotBeNull();
|
|
result!.Success.Should().BeTrue();
|
|
result.Data!.Id.Should().Be(orgId);
|
|
result.Data.Name.Should().Be("Get Test Organization");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetOrganizationById_NonExistingId_Returns404()
|
|
{
|
|
// Arrange
|
|
var nonExistentId = Guid.NewGuid();
|
|
|
|
// Act
|
|
var response = await _client.GetAsync($"/api/v1/organizations/{nonExistentId}");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result!.Success.Should().BeFalse();
|
|
result.Error!.Code.Should().Be("ORG_NOT_FOUND");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetOrganizationBySlug_ExistingSlug_Returns200()
|
|
{
|
|
// Arrange - Create organization first
|
|
var slug = $"slug-test-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Slug Test Organization",
|
|
Slug = slug
|
|
};
|
|
await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
|
|
// Act
|
|
var response = await _client.GetAsync($"/api/v1/organizations/slug/{slug}");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result!.Data!.Slug.Should().Be(slug);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetOrganizationBySlug_NonExistingSlug_Returns404()
|
|
{
|
|
// Act
|
|
var response = await _client.GetAsync("/api/v1/organizations/slug/non-existent-slug-12345");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Update Organization Tests
|
|
|
|
[Fact]
|
|
public async Task UpdateOrganization_ValidRequest_Returns200()
|
|
{
|
|
// Arrange - Create organization first
|
|
var slug = $"update-test-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Original Name",
|
|
Slug = slug,
|
|
Description = "Original Description"
|
|
};
|
|
var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var orgId = createResult!.Data!.Id;
|
|
|
|
var updateRequest = new UpdateOrganizationRequest
|
|
{
|
|
Name = "Updated Name",
|
|
Description = "Updated Description"
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsJsonAsync($"/api/v1/organizations/{orgId}", updateRequest);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
|
|
var result = await response.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
result!.Data!.Name.Should().Be("Updated Name");
|
|
result.Data.Description.Should().Be("Updated Description");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateOrganization_NonExistingId_Returns404()
|
|
{
|
|
// Arrange
|
|
var updateRequest = new UpdateOrganizationRequest
|
|
{
|
|
Name = "Updated Name"
|
|
};
|
|
|
|
// Act
|
|
var response = await _client.PutAsJsonAsync($"/api/v1/organizations/{Guid.NewGuid()}", updateRequest);
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Archive Organization Tests
|
|
|
|
[Fact]
|
|
public async Task ArchiveOrganization_ExistingOrg_Returns200()
|
|
{
|
|
// Arrange - Create organization first
|
|
var slug = $"archive-test-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Archive Test Organization",
|
|
Slug = slug
|
|
};
|
|
var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var orgId = createResult!.Data!.Id;
|
|
|
|
// Act
|
|
var response = await _client.DeleteAsync($"/api/v1/organizations/{orgId}");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveOrganization_NonExistingId_Returns404()
|
|
{
|
|
// Act
|
|
var response = await _client.DeleteAsync($"/api/v1/organizations/{Guid.NewGuid()}");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Hierarchy Tests
|
|
|
|
[Fact]
|
|
public async Task GetOrganizationHierarchy_ExistingOrg_Returns200()
|
|
{
|
|
// Arrange - Create organization first
|
|
var slug = $"hierarchy-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Hierarchy Organization",
|
|
Slug = slug
|
|
};
|
|
var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var orgId = createResult!.Data!.Id;
|
|
|
|
// Act
|
|
var response = await _client.GetAsync($"/api/v1/organizations/{orgId}/hierarchy");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetChildOrganizations_ExistingOrg_Returns200()
|
|
{
|
|
// Arrange - Create parent organization first
|
|
var slug = $"parent-child-{Guid.NewGuid():N}".Substring(0, 30);
|
|
var createRequest = new CreateOrganizationRequest
|
|
{
|
|
Name = "Parent Organization",
|
|
Slug = slug
|
|
};
|
|
var createResponse = await _client.PostAsJsonAsync("/api/v1/organizations", createRequest);
|
|
var createResult = await createResponse.Content.ReadFromJsonAsync<ApiResponse<OrganizationResponse>>();
|
|
var orgId = createResult!.Data!.Id;
|
|
|
|
// Act
|
|
var response = await _client.GetAsync($"/api/v1/organizations/{orgId}/children");
|
|
|
|
// Assert
|
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
/// <summary>
|
|
/// EN: Generate a test JWT token for authentication.
|
|
/// VI: Tạo JWT token test để xác thực.
|
|
/// </summary>
|
|
private static string GenerateTestToken()
|
|
{
|
|
var claims = new[]
|
|
{
|
|
new Claim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()),
|
|
new Claim(JwtRegisteredClaimNames.Email, "test@example.com"),
|
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
|
new Claim("name", "Test User")
|
|
};
|
|
|
|
var key = new SymmetricSecurityKey("test-secret-key-for-testing-purposes-only-32-chars!"u8.ToArray());
|
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
|
|
var token = new JwtSecurityToken(
|
|
issuer: "http://localhost",
|
|
audience: "api",
|
|
claims: claims,
|
|
expires: DateTime.UtcNow.AddHours(1),
|
|
signingCredentials: creds
|
|
);
|
|
|
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
}
|
|
|
|
#endregion
|
|
}
|