feat: Bổ sung unit test cho các mô hình miền AccessRequest và AccessRequestStatus trong IAM, đồng thời loại bỏ tệp docker-compose.yml của dịch vụ merchant.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using IamService.Domain.AggregatesModel.AccessRequestAggregate;
|
||||
|
||||
namespace IamService.UnitTests.Domain.AccessControl;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit tests for AccessRequestStatus enumeration.
|
||||
/// VI: Unit tests cho AccessRequestStatus enumeration.
|
||||
/// </summary>
|
||||
public class AccessRequestStatusTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetAll_Returns6Statuses()
|
||||
{
|
||||
// Act
|
||||
var statuses = AccessRequestStatus.GetAll();
|
||||
|
||||
// Assert
|
||||
statuses.Should().HaveCount(6);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, "Draft")]
|
||||
[InlineData(2, "Pending")]
|
||||
[InlineData(3, "Approved")]
|
||||
[InlineData(4, "Rejected")]
|
||||
[InlineData(5, "Cancelled")]
|
||||
[InlineData(6, "Expired")]
|
||||
public void FromId_ValidId_ReturnsCorrectStatus(int id, string expectedName)
|
||||
{
|
||||
// Act
|
||||
var status = AccessRequestStatus.FromId(id);
|
||||
|
||||
// Assert
|
||||
status.Should().NotBeNull();
|
||||
status!.Name.Should().Be(expectedName);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(7)]
|
||||
[InlineData(-1)]
|
||||
public void FromId_InvalidId_ReturnsNull(int id)
|
||||
{
|
||||
// Act
|
||||
var status = AccessRequestStatus.FromId(id);
|
||||
|
||||
// Assert
|
||||
status.Should().BeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Approved", true)]
|
||||
[InlineData("Rejected", true)]
|
||||
[InlineData("Cancelled", true)]
|
||||
[InlineData("Expired", true)]
|
||||
[InlineData("Draft", false)]
|
||||
[InlineData("Pending", false)]
|
||||
public void IsTerminal_ReturnsCorrectValue(string statusName, bool expectedTerminal)
|
||||
{
|
||||
// Arrange
|
||||
var status = AccessRequestStatus.GetAll().First(s => s.Name == statusName);
|
||||
|
||||
// Assert
|
||||
status.IsTerminal.Should().Be(expectedTerminal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StaticInstances_AreCorrect()
|
||||
{
|
||||
// Assert
|
||||
AccessRequestStatus.Draft.Id.Should().Be(1);
|
||||
AccessRequestStatus.Pending.Id.Should().Be(2);
|
||||
AccessRequestStatus.Approved.Id.Should().Be(3);
|
||||
AccessRequestStatus.Rejected.Id.Should().Be(4);
|
||||
AccessRequestStatus.Cancelled.Id.Should().Be(5);
|
||||
AccessRequestStatus.Expired.Id.Should().Be(6);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
using Xunit;
|
||||
using FluentAssertions;
|
||||
using IamService.Domain.AggregatesModel.AccessRequestAggregate;
|
||||
using IamService.Domain.Events;
|
||||
|
||||
namespace IamService.UnitTests.Domain.AccessControl;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Unit tests for AccessRequest aggregate root.
|
||||
/// VI: Unit tests cho AccessRequest aggregate root.
|
||||
/// </summary>
|
||||
public class AccessRequestTests
|
||||
{
|
||||
private readonly Guid _validRequesterId = Guid.NewGuid();
|
||||
private readonly Guid _validResourceId = Guid.NewGuid();
|
||||
|
||||
#region Creation Tests
|
||||
|
||||
[Fact]
|
||||
public void Create_ValidParameters_CreatesAccessRequestInDraftStatus()
|
||||
{
|
||||
// Arrange & Act
|
||||
var request = AccessRequest.Create(
|
||||
_validRequesterId,
|
||||
"Project",
|
||||
_validResourceId,
|
||||
"read",
|
||||
"Need access to project files");
|
||||
|
||||
// Assert
|
||||
request.Should().NotBeNull();
|
||||
request.Id.Should().NotBeEmpty();
|
||||
request.RequesterId.Should().Be(_validRequesterId);
|
||||
request.ResourceType.Should().Be("Project");
|
||||
request.ResourceId.Should().Be(_validResourceId);
|
||||
request.RequestedPermission.Should().Be("read");
|
||||
request.Justification.Should().Be("Need access to project files");
|
||||
request.Status.Should().Be(AccessRequestStatus.Draft);
|
||||
request.Priority.Should().Be(AccessRequestPriority.Medium);
|
||||
request.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
request.Approvers.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithCustomPriority_SetsPriority()
|
||||
{
|
||||
// Arrange & Act
|
||||
var request = AccessRequest.Create(
|
||||
_validRequesterId,
|
||||
"Project",
|
||||
_validResourceId,
|
||||
"admin",
|
||||
priority: AccessRequestPriority.High);
|
||||
|
||||
// Assert
|
||||
request.Priority.Should().Be(AccessRequestPriority.High);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_EmptyRequesterId_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange & Act
|
||||
var act = () => AccessRequest.Create(Guid.Empty, "Project", _validResourceId, "read");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*Requester ID*empty*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void Create_InvalidResourceType_ThrowsArgumentException(string? resourceType)
|
||||
{
|
||||
// Arrange & Act
|
||||
var act = () => AccessRequest.Create(_validRequesterId, resourceType!, _validResourceId, "read");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*Resource type*empty*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_EmptyResourceId_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange & Act
|
||||
var act = () => AccessRequest.Create(_validRequesterId, "Project", Guid.Empty, "read");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*Resource ID*empty*");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void Create_InvalidPermission_ThrowsArgumentException(string? permission)
|
||||
{
|
||||
// Arrange & Act
|
||||
var act = () => AccessRequest.Create(_validRequesterId, "Project", _validResourceId, permission!);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<ArgumentException>()
|
||||
.WithMessage("*permission*empty*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_RaisesAccessRequestCreatedEvent()
|
||||
{
|
||||
// Arrange & Act
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
|
||||
// Assert
|
||||
request.DomainEvents.Should().ContainSingle();
|
||||
request.DomainEvents.First().Should().BeOfType<AccessRequestCreatedEvent>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AddApprover Tests
|
||||
|
||||
[Fact]
|
||||
public void AddApprover_InDraftStatus_AddsApprover()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
var approver = request.AddApprover(approverId);
|
||||
|
||||
// Assert
|
||||
request.Approvers.Should().HaveCount(1);
|
||||
approver.UserId.Should().Be(approverId);
|
||||
approver.Order.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddApprover_MultipleApprovers_SetsCorrectOrder()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approver1 = Guid.NewGuid();
|
||||
var approver2 = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
request.AddApprover(approver1);
|
||||
request.AddApprover(approver2);
|
||||
|
||||
// Assert
|
||||
request.Approvers.Should().HaveCount(2);
|
||||
request.Approvers.ElementAt(0).Order.Should().Be(1);
|
||||
request.Approvers.ElementAt(1).Order.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddApprover_AfterSubmit_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
var act = () => request.AddApprover(Guid.NewGuid());
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*after request is submitted*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Submit Tests
|
||||
|
||||
[Fact]
|
||||
public void Submit_WithApprovers_ChangeStatusToPending()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
|
||||
// Act
|
||||
request.Submit();
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Pending);
|
||||
request.SubmittedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
request.ExpiresAt.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_WithoutApprovers_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
|
||||
// Act
|
||||
var act = () => request.Submit();
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*At least one approver*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_NotInDraft_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act - Try to submit again
|
||||
var act = () => request.Submit();
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*draft requests*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Submit_RaisesAccessRequestSubmittedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.ClearDomainEvents();
|
||||
|
||||
// Act
|
||||
request.Submit();
|
||||
|
||||
// Assert
|
||||
request.DomainEvents.Should().ContainSingle();
|
||||
request.DomainEvents.First().Should().BeOfType<AccessRequestSubmittedEvent>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Approve Tests
|
||||
|
||||
[Fact]
|
||||
public void Approve_SingleApprover_ChangesStatusToApproved()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
request.AddApprover(approverId);
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Approve(approverId, "Looks good");
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Approved);
|
||||
request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Approve_AllApprovers_ChangesStatusToApproved()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approver1 = Guid.NewGuid();
|
||||
var approver2 = Guid.NewGuid();
|
||||
request.AddApprover(approver1);
|
||||
request.AddApprover(approver2);
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Approve(approver1);
|
||||
request.Approve(approver2);
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Approved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Approve_PartialApproval_StatusRemainsPending()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approver1 = Guid.NewGuid();
|
||||
var approver2 = Guid.NewGuid();
|
||||
request.AddApprover(approver1);
|
||||
request.AddApprover(approver2);
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Approve(approver1);
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Pending);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Approve_NotPending_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
request.AddApprover(approverId);
|
||||
// Not submitted yet
|
||||
|
||||
// Act
|
||||
var act = () => request.Approve(approverId);
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*pending requests*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Approve_NotAnApprover_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act - Random user trying to approve
|
||||
var act = () => request.Approve(Guid.NewGuid());
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*not a pending approver*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reject Tests
|
||||
|
||||
[Fact]
|
||||
public void Reject_PendingRequest_ChangesStatusToRejected()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
request.AddApprover(approverId);
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Reject(approverId, "Not justified");
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Rejected);
|
||||
request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reject_RaisesAccessRequestRejectedEvent()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
request.AddApprover(approverId);
|
||||
request.Submit();
|
||||
request.ClearDomainEvents();
|
||||
|
||||
// Act
|
||||
request.Reject(approverId, "Not justified");
|
||||
|
||||
// Assert
|
||||
request.DomainEvents.Should().ContainSingle();
|
||||
request.DomainEvents.First().Should().BeOfType<AccessRequestRejectedEvent>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cancel Tests
|
||||
|
||||
[Fact]
|
||||
public void Cancel_DraftRequest_ChangesStatusToCancelled()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
|
||||
// Act
|
||||
request.Cancel();
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Cancelled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cancel_PendingRequest_ChangesStatusToCancelled()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Cancel();
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Cancelled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cancel_ApprovedRequest_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
var approverId = Guid.NewGuid();
|
||||
request.AddApprover(approverId);
|
||||
request.Submit();
|
||||
request.Approve(approverId);
|
||||
|
||||
// Act
|
||||
var act = () => request.Cancel();
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*terminal request*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Expire Tests
|
||||
|
||||
[Fact]
|
||||
public void Expire_PendingRequest_ChangesStatusToExpired()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
request.Expire();
|
||||
|
||||
// Assert
|
||||
request.Status.Should().Be(AccessRequestStatus.Expired);
|
||||
request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Expire_NotPending_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
|
||||
// Act
|
||||
var act = () => request.Expire();
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*pending requests*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateJustification Tests
|
||||
|
||||
[Fact]
|
||||
public void UpdateJustification_InDraft_UpdatesJustification()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
|
||||
// Act
|
||||
request.UpdateJustification("Updated justification");
|
||||
|
||||
// Assert
|
||||
request.Justification.Should().Be("Updated justification");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateJustification_AfterSubmit_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read");
|
||||
request.AddApprover(Guid.NewGuid());
|
||||
request.Submit();
|
||||
|
||||
// Act
|
||||
var act = () => request.UpdateJustification("Updated");
|
||||
|
||||
// Assert
|
||||
act.Should().Throw<InvalidOperationException>()
|
||||
.WithMessage("*submitted request*");
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
# EN: Docker Compose for local development
|
||||
# VI: Docker Compose cho phát triển local
|
||||
|
||||
services:
|
||||
merchant-service-net:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: merchant-service-net
|
||||
ports:
|
||||
- "5005:8080"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ConnectionStrings__DefaultConnection=Host=ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech;Database=merchant_service;Username=neondb_owner;Password=npg_Ssfy6HKO0cXI;SSL Mode=Require
|
||||
- Jwt__Authority=http://iam-service-net:8080
|
||||
- Jwt__Audience=goodgo-api
|
||||
- Jwt__RequireHttpsMetadata=false
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- microservices-network
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
microservices-network:
|
||||
external: true
|
||||
@@ -34,7 +34,6 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
public SignUploadCommandHandlerTests()
|
||||
{
|
||||
// EN: Setup mocks / VI: Setup mocks
|
||||
_quotaRepository = Substitute.For<IQuotaRepository>();
|
||||
_storageProviderFactory = Substitute.For<IStorageProviderFactory>();
|
||||
_storageProvider = Substitute.For<IStorageProvider>();
|
||||
@@ -68,6 +67,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(expectedUrl);
|
||||
@@ -92,6 +93,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -116,7 +119,7 @@ public class SignUploadCommandHandlerTests
|
||||
TestUserId,
|
||||
TestFileName,
|
||||
TestContentType,
|
||||
MaxFileSize + 1); // Exceeds max
|
||||
MaxFileSize + 1);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
@@ -136,11 +139,13 @@ public class SignUploadCommandHandlerTests
|
||||
TestUserId,
|
||||
TestFileName,
|
||||
TestContentType,
|
||||
MaxFileSize); // Exactly at limit
|
||||
MaxFileSize);
|
||||
|
||||
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -161,7 +166,8 @@ public class SignUploadCommandHandlerTests
|
||||
{
|
||||
// Arrange
|
||||
var command = CreateValidCommand();
|
||||
var quota = CreateQuotaWithSpace(0); // No space left
|
||||
// Create quota with no space left
|
||||
var quota = new UserStorageQuota(TestUserId, maxStorageBytes: TestFileSize - 1);
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
@@ -184,6 +190,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -213,6 +221,8 @@ public class SignUploadCommandHandlerTests
|
||||
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -238,6 +248,8 @@ public class SignUploadCommandHandlerTests
|
||||
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -263,6 +275,8 @@ public class SignUploadCommandHandlerTests
|
||||
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -283,6 +297,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -303,6 +319,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns("https://example.com/upload");
|
||||
@@ -328,6 +346,8 @@ public class SignUploadCommandHandlerTests
|
||||
|
||||
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any<CancellationToken>())
|
||||
.Returns(quota);
|
||||
_storageProvider.EnsureBucketExistsAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
_storageProvider.GetPreSignedUploadUrlAsync(
|
||||
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new Exception("Storage connection failed"));
|
||||
@@ -370,15 +390,13 @@ public class SignUploadCommandHandlerTests
|
||||
TestFileSize);
|
||||
}
|
||||
|
||||
private static UserStorageQuota CreateQuotaWithSpace(long availableSpace)
|
||||
/// <summary>
|
||||
/// EN: Create a real UserStorageQuota with available space.
|
||||
/// VI: Tạo real UserStorageQuota với dung lượng khả dụng.
|
||||
/// </summary>
|
||||
private static UserStorageQuota CreateQuotaWithSpace(long maxStorage)
|
||||
{
|
||||
var quota = Substitute.For<UserStorageQuota>();
|
||||
quota.CanUpload(Arg.Any<long>()).Returns(callInfo =>
|
||||
{
|
||||
var requestedSize = callInfo.Arg<long>();
|
||||
return requestedSize <= availableSpace;
|
||||
});
|
||||
return quota;
|
||||
return new UserStorageQuota(TestUserId, maxStorageBytes: maxStorage);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user