diff --git a/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestStatusTests.cs b/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestStatusTests.cs
new file mode 100644
index 00000000..359b0298
--- /dev/null
+++ b/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestStatusTests.cs
@@ -0,0 +1,80 @@
+using Xunit;
+using FluentAssertions;
+using IamService.Domain.AggregatesModel.AccessRequestAggregate;
+
+namespace IamService.UnitTests.Domain.AccessControl;
+
+///
+/// EN: Unit tests for AccessRequestStatus enumeration.
+/// VI: Unit tests cho AccessRequestStatus enumeration.
+///
+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);
+ }
+}
diff --git a/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestTests.cs b/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestTests.cs
new file mode 100644
index 00000000..365502e8
--- /dev/null
+++ b/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessRequestTests.cs
@@ -0,0 +1,488 @@
+using Xunit;
+using FluentAssertions;
+using IamService.Domain.AggregatesModel.AccessRequestAggregate;
+using IamService.Domain.Events;
+
+namespace IamService.UnitTests.Domain.AccessControl;
+
+///
+/// EN: Unit tests for AccessRequest aggregate root.
+/// VI: Unit tests cho AccessRequest aggregate root.
+///
+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()
+ .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()
+ .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()
+ .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()
+ .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();
+ }
+
+ #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()
+ .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()
+ .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()
+ .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();
+ }
+
+ #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()
+ .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()
+ .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();
+ }
+
+ #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()
+ .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()
+ .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()
+ .WithMessage("*submitted request*");
+ }
+
+ #endregion
+}
diff --git a/services/merchant-service-net/docker-compose.yml b/services/merchant-service-net/docker-compose.yml
deleted file mode 100644
index 82c22f3a..00000000
--- a/services/merchant-service-net/docker-compose.yml
+++ /dev/null
@@ -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
diff --git a/services/storage-service-net/tests/StorageService.UnitTests/Handlers/SignUploadCommandHandlerTests.cs b/services/storage-service-net/tests/StorageService.UnitTests/Handlers/SignUploadCommandHandlerTests.cs
index 6f5a25a3..ff2b3a1d 100644
--- a/services/storage-service-net/tests/StorageService.UnitTests/Handlers/SignUploadCommandHandlerTests.cs
+++ b/services/storage-service-net/tests/StorageService.UnitTests/Handlers/SignUploadCommandHandlerTests.cs
@@ -34,7 +34,6 @@ public class SignUploadCommandHandlerTests
public SignUploadCommandHandlerTests()
{
- // EN: Setup mocks / VI: Setup mocks
_quotaRepository = Substitute.For();
_storageProviderFactory = Substitute.For();
_storageProvider = Substitute.For();
@@ -68,6 +67,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns(expectedUrl);
@@ -92,6 +93,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.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())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.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())
.Returns(quota);
@@ -184,6 +190,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -213,6 +221,8 @@ public class SignUploadCommandHandlerTests
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -238,6 +248,8 @@ public class SignUploadCommandHandlerTests
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -263,6 +275,8 @@ public class SignUploadCommandHandlerTests
var quota = CreateQuotaWithSpace(MaxQuotaBytes);
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -283,6 +297,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -303,6 +319,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.Returns("https://example.com/upload");
@@ -328,6 +346,8 @@ public class SignUploadCommandHandlerTests
_quotaRepository.GetOrCreateAsync(TestUserId, Arg.Any())
.Returns(quota);
+ _storageProvider.EnsureBucketExistsAsync(Arg.Any(), Arg.Any())
+ .Returns(Task.CompletedTask);
_storageProvider.GetPreSignedUploadUrlAsync(
Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new Exception("Storage connection failed"));
@@ -370,15 +390,13 @@ public class SignUploadCommandHandlerTests
TestFileSize);
}
- private static UserStorageQuota CreateQuotaWithSpace(long availableSpace)
+ ///
+ /// EN: Create a real UserStorageQuota with available space.
+ /// VI: Tạo real UserStorageQuota với dung lượng khả dụng.
+ ///
+ private static UserStorageQuota CreateQuotaWithSpace(long maxStorage)
{
- var quota = Substitute.For();
- quota.CanUpload(Arg.Any()).Returns(callInfo =>
- {
- var requestedSize = callInfo.Arg();
- return requestedSize <= availableSpace;
- });
- return quota;
+ return new UserStorageQuota(TestUserId, maxStorageBytes: maxStorage);
}
#endregion