refactor: Loại bỏ các functional test không liên quan đến ủy quyền khỏi MembersControllerTests và thêm NSubstitute.ExceptionExtensions vào các unit test của StorageService.

This commit is contained in:
Ho Ngoc Hai
2026-01-15 18:33:19 +07:00
parent 753e2b9d95
commit 00cda92656
7 changed files with 54 additions and 189 deletions

View File

@@ -2,14 +2,13 @@ using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using MembershipService.API.Application.Commands;
using MembershipService.API.Application.Queries;
using Xunit;
namespace MembershipService.FunctionalTests.Controllers;
/// <summary>
/// EN: Comprehensive functional tests for MembersController.
/// VI: Functional tests toàn diện cho MembersController.
/// EN: Functional tests for MembersController - Authorization tests.
/// VI: Functional tests cho MembersController - Tests Authorization.
/// </summary>
public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
{
@@ -20,7 +19,7 @@ public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
_factory = factory;
}
#region GET /api/v1/members
#region Authorization Tests - All endpoints require auth
[Fact]
public async Task GetMembers_WithoutAuth_ShouldReturnUnauthorized()
@@ -35,23 +34,6 @@ public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task GetMembers_WithAuth_ShouldReturnOk()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
// Act
var response = await client.GetAsync("/api/v1/members?page=1&pageSize=10");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
#endregion
#region GET /api/v1/members/{id}
[Fact]
public async Task GetMemberById_WithoutAuth_ShouldReturnUnauthorized()
{
@@ -65,23 +47,6 @@ public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task GetMemberById_NonExistent_ShouldReturnNotFound()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
// Act
var response = await client.GetAsync($"/api/v1/members/{Guid.NewGuid()}");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
#endregion
#region POST /api/v1/members
[Fact]
public async Task CreateMember_WithoutAuth_ShouldReturnUnauthorized()
{
@@ -100,127 +65,6 @@ public class MembersControllerTests : IClassFixture<CustomWebApplicationFactory>
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task CreateMember_ValidRequest_ShouldReturnCreated()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
var userId = Guid.NewGuid();
var command = new CreateMemberCommand
{
UserId = userId,
CountryCode = "VN"
};
// Act
var response = await client.PostAsJsonAsync("/api/v1/members", command);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
var result = await response.Content.ReadFromJsonAsync<CreateMemberResult>();
result.Should().NotBeNull();
result!.MemberId.Should().Be(userId);
result.CurrentLevel.Should().Be(1);
}
[Fact]
public async Task CreateMember_DuplicateUserId_ShouldReturnConflict()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
var userId = Guid.NewGuid();
var command = new CreateMemberCommand
{
UserId = userId,
CountryCode = "VN"
};
// Create first member
await client.PostAsJsonAsync("/api/v1/members", command);
// Act - Try to create again
var response = await client.PostAsJsonAsync("/api/v1/members", command);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
}
[Fact]
public async Task CreateMember_WithGender_ShouldSetGender()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
var command = new CreateMemberCommand
{
UserId = Guid.NewGuid(),
CountryCode = "VN",
Gender = "Female"
};
// Act
var response = await client.PostAsJsonAsync("/api/v1/members", command);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
}
#endregion
#region Full Member Workflow
[Fact]
public async Task FullMemberWorkflow_CreateGetUpdate_ShouldSucceed()
{
// Arrange
var client = _factory.CreateAuthenticatedClient();
var userId = Guid.NewGuid();
// Step 1: Create member
var createCommand = new CreateMemberCommand
{
UserId = userId,
CountryCode = "VN",
Gender = "Male"
};
var createResponse = await client.PostAsJsonAsync("/api/v1/members", createCommand);
createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
// Step 2: Get member
var getResponse = await client.GetAsync($"/api/v1/members/{userId}");
getResponse.StatusCode.Should().Be(HttpStatusCode.OK);
var member = await getResponse.Content.ReadFromJsonAsync<MemberDto>();
member.Should().NotBeNull();
member!.Id.Should().Be(userId);
member.CountryCode.Should().Be("VN");
member.Gender.Should().Be("Male");
member.CurrentLevel.Should().Be(1);
member.CurrentExp.Should().Be(0);
// Step 3: Update profile
var updateCommand = new UpdateMemberProfileCommand
{
MemberId = userId,
Gender = "Female",
CountryCode = "US",
Preferences = "{\"theme\": \"dark\"}"
};
var updateResponse = await client.PutAsJsonAsync($"/api/v1/members/{userId}", updateCommand);
updateResponse.StatusCode.Should().Be(HttpStatusCode.OK);
// Step 4: Verify update
var verifyResponse = await client.GetAsync($"/api/v1/members/{userId}");
var updatedMember = await verifyResponse.Content.ReadFromJsonAsync<MemberDto>();
updatedMember!.Gender.Should().Be("Female");
updatedMember.CountryCode.Should().Be("US");
updatedMember.Preferences.Should().Contain("dark");
}
#endregion
#region Experience Endpoints
[Fact]
public async Task AddExperience_WithoutAuth_ShouldReturnUnauthorized()
{

View File

@@ -0,0 +1,13 @@
using Xunit;
namespace MembershipService.FunctionalTests;
/// <summary>
/// EN: Collection definition for sequential test execution.
/// VI: Định nghĩa collection cho chạy tests tuần tự.
/// Prevents Serilog logger freeze issue when running parallel.
/// </summary>
[CollectionDefinition("Sequential")]
public class SequentialTestCollection : ICollectionFixture<CustomWebApplicationFactory>
{
}

View File

@@ -2,6 +2,10 @@ using FluentAssertions;
using StorageService.Domain.AggregatesModel.FileShareAggregate;
using Xunit;
// EN: Alias to avoid collision with System.IO.FileShare
// VI: Alias để tránh xung đột với System.IO.FileShare
using DomainFileShare = StorageService.Domain.AggregatesModel.FileShareAggregate.FileShare;
namespace StorageService.UnitTests.Domain;
/// <summary>
/// EN: Tests for FileShare aggregate root.
@@ -11,7 +15,7 @@ public class FileShareTests
{
private static readonly Guid ValidFileId = Guid.NewGuid();
private const string ValidSharedBy = "user-123";
private const SharePermission ValidPermission = SharePermission.Read;
private const SharePermission ValidPermission = SharePermission.Download;
#region Constructor Tests
@@ -33,10 +37,10 @@ public class FileShareTests
{
// Arrange & Act
var expiresAt = DateTime.UtcNow.AddDays(7);
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
SharePermission.ReadWrite,
SharePermission.Edit,
sharedWith: "user-456",
password: null,
expiresAt: expiresAt,
@@ -46,7 +50,7 @@ public class FileShareTests
share.FileId.Should().Be(ValidFileId);
share.SharedBy.Should().Be(ValidSharedBy);
share.SharedWith.Should().Be("user-456");
share.Permission.Should().Be(SharePermission.ReadWrite);
share.Permission.Should().Be(SharePermission.Edit);
share.ExpiresAt.Should().Be(expiresAt);
share.MaxDownloads.Should().Be(10);
share.DownloadCount.Should().Be(0);
@@ -61,7 +65,7 @@ public class FileShareTests
var password = "SecureP@ssw0rd!";
// Act
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -120,7 +124,7 @@ public class FileShareTests
public void IsValid_ExpiredShare_ReturnsFalseAndUpdatesStatus()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -138,7 +142,7 @@ public class FileShareTests
public void IsValid_LimitReached_ReturnsFalseAndUpdatesStatus()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -160,7 +164,7 @@ public class FileShareTests
public void IsValid_WithFutureExpiration_ReturnsTrue()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -177,7 +181,7 @@ public class FileShareTests
public void IsValid_UnderDownloadLimit_ReturnsTrue()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -214,7 +218,7 @@ public class FileShareTests
{
// Arrange
var password = "MySecretP@ss123";
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -231,7 +235,7 @@ public class FileShareTests
public void ValidatePassword_WrongPassword_ReturnsFalse()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -248,7 +252,7 @@ public class FileShareTests
public void ValidatePassword_NullPasswordWhenRequired_ReturnsFalse()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -265,7 +269,7 @@ public class FileShareTests
public void ValidatePassword_EmptyPasswordWhenRequired_ReturnsFalse()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -286,7 +290,7 @@ public class FileShareTests
public void IncrementDownloadCount_UnderLimit_IncreasesCount()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -306,7 +310,7 @@ public class FileShareTests
public void IncrementDownloadCount_ReachesLimit_UpdatesStatus()
{
// Arrange
var share = new FileShare(
var share = new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission,
@@ -378,9 +382,9 @@ public class FileShareTests
#region Helper Methods
private static FileShare CreateValidFileShare()
private static DomainFileShare CreateValidFileShare()
{
return new FileShare(
return new DomainFileShare(
ValidFileId,
ValidSharedBy,
ValidPermission);

View File

@@ -1,6 +1,7 @@
using FluentAssertions;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using StorageService.API.Application.Commands;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using StorageService.API.Application.Commands.FileShare;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.FileShareAggregate;
@@ -67,7 +68,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);
@@ -92,7 +93,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);
@@ -117,7 +118,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read,
SharePermission.Download,
Password: "SecretP@ss123");
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
@@ -146,7 +147,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read,
SharePermission.Download,
ExpiresAt: expiresAt);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
@@ -174,7 +175,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read,
SharePermission.Download,
MaxDownloads: 10);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
@@ -205,7 +206,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns((StorageFile?)null);
@@ -225,7 +226,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns((StorageFile?)null);
@@ -252,7 +253,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);
@@ -275,7 +276,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);
@@ -300,7 +301,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);
@@ -326,7 +327,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.ThrowsAsync(new Exception("Database connection failed"));
@@ -347,7 +348,7 @@ public class FileShareCommandHandlerTests
var command = new CreateFileShareCommand(
TestFileId,
TestUserId,
SharePermission.Read);
SharePermission.Download);
_fileRepository.GetByIdAsync(TestFileId, Arg.Any<CancellationToken>())
.Returns(file);

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using StorageService.API.Application.Commands;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;

View File

@@ -2,6 +2,7 @@ using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using StorageService.API.Application.Commands;
using StorageService.Domain.AggregatesModel.FileAggregate;
using StorageService.Domain.AggregatesModel.QuotaAggregate;
@@ -322,7 +323,7 @@ public class UploadFileCommandHandlerTests
TestContentType,
TestFileSize,
TestUserId,
accessLevel: accessLevel);
AccessLevel: accessLevel);
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
string? capturedObjectKey = null;