diff --git a/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/AdsBillingControllerTests.cs b/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/AdsBillingControllerTests.cs
new file mode 100644
index 00000000..21d2a2af
--- /dev/null
+++ b/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/AdsBillingControllerTests.cs
@@ -0,0 +1,133 @@
+using System.Net;
+using System.Net.Http.Json;
+using System.Text.Json;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace AdsBillingService.FunctionalTests.Controllers;
+
+///
+/// EN: Functional tests for billing account, credit line, and invoice admin flows.
+/// VI: Functional tests cho luồng tài khoản billing, hạn mức tín dụng và quản trị hóa đơn.
+///
+public class AdsBillingControllerTests : IClassFixture
+{
+ private readonly HttpClient _client;
+
+ public AdsBillingControllerTests(CustomWebApplicationFactory factory)
+ {
+ _client = factory.CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false,
+ });
+ }
+
+ [Fact]
+ public async Task BillingAccountFlow_ShouldCreateAccount_AddFunds_AndReturnBalance()
+ {
+ // Arrange
+ var advertiserId = Guid.NewGuid();
+ var createRequest = new
+ {
+ advertiserId,
+ walletId = Guid.NewGuid(),
+ paymentMethod = "prepaid",
+ };
+
+ // Act
+ var createResponse = await _client.PostAsJsonAsync("/api/v1/ads-billing/accounts", createRequest);
+ var accountId = await createResponse.Content.ReadFromJsonAsync();
+
+ var addFundsResponse = await _client.PostAsJsonAsync(
+ $"/api/v1/ads-billing/accounts/{accountId}/add-funds",
+ new { amount = 500m });
+
+ var balanceResponse = await _client.GetAsync($"/api/v1/ads-billing/accounts/{accountId}/balance");
+
+ // Assert
+ createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+ accountId.Should().NotBe(Guid.Empty);
+ addFundsResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+ balanceResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ var balancePayload = await balanceResponse.Content.ReadFromJsonAsync();
+ balancePayload.GetProperty("balance").GetDecimal().Should().Be(500m);
+ }
+
+ [Fact]
+ public async Task CreditLineRequest_ShouldIncreaseCreditLimit()
+ {
+ // Arrange
+ var advertiserId = Guid.NewGuid();
+ var createRequest = new
+ {
+ advertiserId,
+ walletId = Guid.NewGuid(),
+ paymentMethod = "postpaid",
+ };
+
+ var createResponse = await _client.PostAsJsonAsync("/api/v1/ads-billing/accounts", createRequest);
+ createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+
+ // Act
+ var increaseResponse = await _client.PostAsJsonAsync(
+ "/api/v1/ads-billing/credit-lines/request",
+ new
+ {
+ advertiserId,
+ requestedAmount = 1500m,
+ reason = "Scale campaign budget",
+ });
+
+ var creditLineResponse = await _client.GetAsync($"/api/v1/ads-billing/credit-lines/{advertiserId}");
+
+ // Assert
+ increaseResponse.StatusCode.Should().Be(HttpStatusCode.Accepted);
+ creditLineResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ var creditPayload = await creditLineResponse.Content.ReadFromJsonAsync();
+ creditPayload.GetProperty("creditLimit").GetDecimal().Should().Be(1500m);
+ }
+
+ [Fact]
+ public async Task RegenerateInvoice_WithoutCharges_ShouldReturnBadRequest()
+ {
+ // Arrange
+ var advertiserId = Guid.NewGuid();
+ var createResponse = await _client.PostAsJsonAsync(
+ "/api/v1/ads-billing/accounts",
+ new
+ {
+ advertiserId,
+ walletId = Guid.NewGuid(),
+ paymentMethod = "postpaid",
+ });
+
+ createResponse.StatusCode.Should().Be(HttpStatusCode.Created);
+ var accountId = await createResponse.Content.ReadFromJsonAsync();
+
+ // Act
+ var regenerateResponse = await _client.PostAsJsonAsync(
+ "/api/v1/admin/ads-billing/invoices/regenerate",
+ new
+ {
+ billingAccountId = accountId,
+ startDate = DateTime.UtcNow.AddDays(-7),
+ endDate = DateTime.UtcNow,
+ });
+
+ // Assert
+ regenerateResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
+ }
+
+ [Fact]
+ public async Task HealthCheck_ShouldReturnHealthy()
+ {
+ // Act
+ var response = await _client.GetAsync("/health/live");
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+}
diff --git a/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/SamplesControllerTests.cs b/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/SamplesControllerTests.cs
deleted file mode 100644
index 6673c957..00000000
--- a/services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/SamplesControllerTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Net;
-using System.Net.Http.Json;
-using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Xunit;
-
-namespace AdsBillingService.FunctionalTests.Controllers;
-
-///
-/// EN: Functional tests for Samples API endpoints.
-/// VI: Functional tests cho các endpoints API Samples.
-///
-public class SamplesControllerTests : IClassFixture
-{
- private readonly HttpClient _client;
-
- public SamplesControllerTests(CustomWebApplicationFactory factory)
- {
- _client = factory.CreateClient(new WebApplicationFactoryClientOptions
- {
- AllowAutoRedirect = false
- });
- }
-
- [Fact]
- public async Task GetSamples_ShouldReturnOkWithEmptyList()
- {
- // Act
- var response = await _client.GetAsync("/api/v1/samples");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- var content = await response.Content.ReadFromJsonAsync>>();
- content?.Success.Should().BeTrue();
- }
-
- [Fact]
- public async Task CreateSample_WithValidData_ShouldReturnCreated()
- {
- // Arrange
- var request = new { Name = "Test Sample", Description = "Test Description" };
-
- // Act
- var response = await _client.PostAsJsonAsync("/api/v1/samples", request);
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.Created);
- var content = await response.Content.ReadFromJsonAsync>();
- content?.Success.Should().BeTrue();
- content?.Data?.Id.Should().NotBeEmpty();
- }
-
- [Fact]
- public async Task GetSample_WithInvalidId_ShouldReturnNotFound()
- {
- // Arrange
- var invalidId = Guid.NewGuid();
-
- // Act
- var response = await _client.GetAsync($"/api/v1/samples/{invalidId}");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.NotFound);
- }
-
- [Fact]
- public async Task HealthCheck_ShouldReturnHealthy()
- {
- // Act
- var response = await _client.GetAsync("/health/live");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- }
-
- // EN: Helper DTOs for deserialization
- // VI: Helper DTOs để deserialize
- private record ApiResponse(bool Success, T? Data);
- private record CreateSampleResult(Guid Id);
-}
diff --git a/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/AdsServingControllerTests.cs b/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/AdsServingControllerTests.cs
new file mode 100644
index 00000000..bddf1f7f
--- /dev/null
+++ b/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/AdsServingControllerTests.cs
@@ -0,0 +1,105 @@
+using System.Net;
+using System.Net.Http.Json;
+using System.Text.Json;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace AdsServingService.FunctionalTests.Controllers;
+
+///
+/// EN: Functional tests for ads serving and admin auction endpoints.
+/// VI: Functional tests cho endpoint phục vụ quảng cáo và quản trị auction.
+///
+public class AdsServingControllerTests : IClassFixture
+{
+ private readonly HttpClient _client;
+
+ public AdsServingControllerTests(CustomWebApplicationFactory factory)
+ {
+ _client = factory.CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false,
+ });
+ }
+
+ [Fact]
+ public async Task ServeAd_ShouldReturnServedAdPayload()
+ {
+ // Arrange
+ var request = new
+ {
+ userId = Guid.NewGuid(),
+ placementType = "feed",
+ userContext = new Dictionary
+ {
+ ["segment"] = "high-intent",
+ },
+ };
+
+ // Act
+ var response = await _client.PostAsJsonAsync("/api/v1/ads/serve", request);
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ var content = await response.Content.ReadFromJsonAsync();
+ var adId = content.GetProperty("adId").GetGuid();
+ var finalPrice = content.GetProperty("finalPrice").GetDecimal();
+
+ adId.Should().NotBe(Guid.Empty);
+ finalPrice.Should().BeGreaterThan(0);
+ }
+
+ [Fact]
+ public async Task GetAuctionStatistics_AfterServe_ShouldReflectPersistedAuctions()
+ {
+ // Arrange
+ var request = new
+ {
+ userId = Guid.NewGuid(),
+ placementType = "story",
+ userContext = new Dictionary(),
+ };
+
+ // Act
+ var serveResponse = await _client.PostAsJsonAsync("/api/v1/ads/serve", request);
+ var statsResponse = await _client.GetAsync("/api/v1/admin/auctions/statistics");
+
+ // Assert
+ serveResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+ statsResponse.StatusCode.Should().Be(HttpStatusCode.OK);
+
+ var stats = await statsResponse.Content.ReadFromJsonAsync();
+ stats.GetProperty("totalAuctions").GetInt32().Should().BeGreaterThanOrEqualTo(1);
+ stats.GetProperty("totalBidsPlaced").GetInt64().Should().BeGreaterThanOrEqualTo(2);
+ }
+
+ [Fact]
+ public async Task TrackImpression_ShouldReturnAccepted()
+ {
+ // Arrange
+ var request = new
+ {
+ adId = Guid.NewGuid(),
+ userId = Guid.NewGuid(),
+ timestamp = DateTime.UtcNow,
+ };
+
+ // Act
+ var response = await _client.PostAsJsonAsync("/api/v1/ads/events/impression", request);
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.Accepted);
+ }
+
+ [Fact]
+ public async Task HealthCheck_ShouldReturnHealthy()
+ {
+ // Act
+ var response = await _client.GetAsync("/health/live");
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+}
diff --git a/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/SamplesControllerTests.cs b/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/SamplesControllerTests.cs
deleted file mode 100644
index 42917d3f..00000000
--- a/services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/SamplesControllerTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Net;
-using System.Net.Http.Json;
-using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Xunit;
-
-namespace AdsServingService.FunctionalTests.Controllers;
-
-///
-/// EN: Functional tests for Samples API endpoints.
-/// VI: Functional tests cho các endpoints API Samples.
-///
-public class SamplesControllerTests : IClassFixture
-{
- private readonly HttpClient _client;
-
- public SamplesControllerTests(CustomWebApplicationFactory factory)
- {
- _client = factory.CreateClient(new WebApplicationFactoryClientOptions
- {
- AllowAutoRedirect = false
- });
- }
-
- [Fact]
- public async Task GetSamples_ShouldReturnOkWithEmptyList()
- {
- // Act
- var response = await _client.GetAsync("/api/v1/samples");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- var content = await response.Content.ReadFromJsonAsync>>();
- content?.Success.Should().BeTrue();
- }
-
- [Fact]
- public async Task CreateSample_WithValidData_ShouldReturnCreated()
- {
- // Arrange
- var request = new { Name = "Test Sample", Description = "Test Description" };
-
- // Act
- var response = await _client.PostAsJsonAsync("/api/v1/samples", request);
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.Created);
- var content = await response.Content.ReadFromJsonAsync>();
- content?.Success.Should().BeTrue();
- content?.Data?.Id.Should().NotBeEmpty();
- }
-
- [Fact]
- public async Task GetSample_WithInvalidId_ShouldReturnNotFound()
- {
- // Arrange
- var invalidId = Guid.NewGuid();
-
- // Act
- var response = await _client.GetAsync($"/api/v1/samples/{invalidId}");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.NotFound);
- }
-
- [Fact]
- public async Task HealthCheck_ShouldReturnHealthy()
- {
- // Act
- var response = await _client.GetAsync("/health/live");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- }
-
- // EN: Helper DTOs for deserialization
- // VI: Helper DTOs để deserialize
- private record ApiResponse(bool Success, T? Data);
- private record CreateSampleResult(Guid Id);
-}
diff --git a/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/HealthChecksControllerTests.cs b/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/HealthChecksControllerTests.cs
new file mode 100644
index 00000000..7fc14eb6
--- /dev/null
+++ b/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/HealthChecksControllerTests.cs
@@ -0,0 +1,33 @@
+using System.Net;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace OrderService.FunctionalTests.Controllers;
+
+///
+/// EN: Functional tests for health check endpoints.
+/// VI: Functional tests cho các endpoint kiểm tra sức khỏe.
+///
+public class HealthChecksControllerTests : IClassFixture
+{
+ private readonly HttpClient _client;
+
+ public HealthChecksControllerTests(CustomWebApplicationFactory factory)
+ {
+ _client = factory.CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false,
+ });
+ }
+
+ [Fact]
+ public async Task LiveHealthCheck_ShouldReturnOk()
+ {
+ // Act
+ var response = await _client.GetAsync("/health/live");
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+}
diff --git a/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/SamplesControllerTests.cs b/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/SamplesControllerTests.cs
deleted file mode 100644
index e2427507..00000000
--- a/services/order-service-net/tests/OrderService.FunctionalTests/Controllers/SamplesControllerTests.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System.Net;
-using System.Net.Http.Json;
-using FluentAssertions;
-using Microsoft.AspNetCore.Mvc.Testing;
-using Xunit;
-
-namespace OrderService.FunctionalTests.Controllers;
-
-///
-/// EN: Functional tests for Samples API endpoints.
-/// VI: Functional tests cho các endpoints API Samples.
-///
-public class SamplesControllerTests : IClassFixture
-{
- private readonly HttpClient _client;
-
- public SamplesControllerTests(CustomWebApplicationFactory factory)
- {
- _client = factory.CreateClient(new WebApplicationFactoryClientOptions
- {
- AllowAutoRedirect = false
- });
- }
-
- [Fact]
- public async Task GetSamples_ShouldReturnOkWithEmptyList()
- {
- // Act
- var response = await _client.GetAsync("/api/v1/samples");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- var content = await response.Content.ReadFromJsonAsync>>();
- content?.Success.Should().BeTrue();
- }
-
- [Fact]
- public async Task CreateSample_WithValidData_ShouldReturnCreated()
- {
- // Arrange
- var request = new { Name = "Test Sample", Description = "Test Description" };
-
- // Act
- var response = await _client.PostAsJsonAsync("/api/v1/samples", request);
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.Created);
- var content = await response.Content.ReadFromJsonAsync>();
- content?.Success.Should().BeTrue();
- content?.Data?.Id.Should().NotBeEmpty();
- }
-
- [Fact]
- public async Task GetSample_WithInvalidId_ShouldReturnNotFound()
- {
- // Arrange
- var invalidId = Guid.NewGuid();
-
- // Act
- var response = await _client.GetAsync($"/api/v1/samples/{invalidId}");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.NotFound);
- }
-
- [Fact]
- public async Task HealthCheck_ShouldReturnHealthy()
- {
- // Act
- var response = await _client.GetAsync("/health/live");
-
- // Assert
- response.StatusCode.Should().Be(HttpStatusCode.OK);
- }
-
- // EN: Helper DTOs for deserialization
- // VI: Helper DTOs để deserialize
- private record ApiResponse(bool Success, T? Data);
- private record CreateSampleResult(Guid Id);
-}
diff --git a/services/order-service-net/tests/OrderService.UnitTests/Domain/OrderAggregateTests.cs b/services/order-service-net/tests/OrderService.UnitTests/Domain/OrderAggregateTests.cs
new file mode 100644
index 00000000..cd594595
--- /dev/null
+++ b/services/order-service-net/tests/OrderService.UnitTests/Domain/OrderAggregateTests.cs
@@ -0,0 +1,97 @@
+using FluentAssertions;
+using OrderService.Domain.AggregatesModel.OrderAggregate;
+using OrderService.Domain.Exceptions;
+using Xunit;
+
+namespace OrderService.UnitTests.Domain;
+
+///
+/// EN: Unit tests for Order aggregate domain behavior.
+/// VI: Unit tests cho hành vi domain của aggregate Order.
+///
+public class OrderAggregateTests
+{
+ [Fact]
+ public void CreateOrder_WithValidShopId_ShouldStartInDraftStatus()
+ {
+ // Arrange
+ var shopId = Guid.NewGuid();
+
+ // Act
+ var order = new Order(shopId);
+
+ // Assert
+ order.Id.Should().NotBe(Guid.Empty);
+ order.ShopId.Should().Be(shopId);
+ order.Status.Should().Be(OrderStatus.Draft);
+ order.TotalAmount.Should().Be(0m);
+ order.DomainEvents.Should().NotBeEmpty();
+ }
+
+ [Fact]
+ public void AddItem_InDraftStatus_ShouldRecalculateTotalAmount()
+ {
+ // Arrange
+ var order = new Order(Guid.NewGuid());
+ var firstItem = new OrderItem(Guid.NewGuid(), "Coffee", "PreparedFood", 2, 30_000m);
+ var secondItem = new OrderItem(Guid.NewGuid(), "Cake", "PreparedFood", 1, 45_000m);
+
+ // Act
+ order.AddItem(firstItem);
+ order.AddItem(secondItem);
+
+ // Assert
+ order.Items.Should().HaveCount(2);
+ order.TotalAmount.Should().Be(105_000m);
+ }
+
+ [Fact]
+ public void MarkAsValidated_WithoutItems_ShouldThrowDomainException()
+ {
+ // Arrange
+ var order = new Order(Guid.NewGuid());
+
+ // Act
+ var act = () => order.MarkAsValidated();
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Cannot validate order with no items");
+ }
+
+ [Fact]
+ public void FullLifecycle_DraftToCompleted_ShouldUpdateStatusSequentially()
+ {
+ // Arrange
+ var order = new Order(Guid.NewGuid());
+ order.AddItem(new OrderItem(Guid.NewGuid(), "Shoes", "Physical", 1, 1_200_000m));
+
+ // Act
+ order.MarkAsValidated();
+ order.MarkAsPaid();
+ order.MarkAsProcessing();
+ order.MarkAsCompleted();
+
+ // Assert
+ order.Status.Should().Be(OrderStatus.Completed);
+ }
+
+ [Fact]
+ public void Cancel_CompletedOrder_ShouldThrowDomainException()
+ {
+ // Arrange
+ var order = new Order(Guid.NewGuid());
+ order.AddItem(new OrderItem(Guid.NewGuid(), "Laptop", "Physical", 1, 25_000_000m));
+ order.MarkAsValidated();
+ order.MarkAsPaid();
+ order.MarkAsProcessing();
+ order.MarkAsCompleted();
+
+ // Act
+ var act = () => order.Cancel("Customer changed mind");
+
+ // Assert
+ act.Should().Throw()
+ .WithMessage("Cannot cancel completed order");
+ }
+}
diff --git a/services/order-service-net/tests/OrderService.UnitTests/Domain/SampleAggregateTests.cs b/services/order-service-net/tests/OrderService.UnitTests/Domain/SampleAggregateTests.cs
deleted file mode 100644
index 3df2bcee..00000000
--- a/services/order-service-net/tests/OrderService.UnitTests/Domain/SampleAggregateTests.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using FluentAssertions;
-using OrderService.Domain.AggregatesModel.SampleAggregate;
-using OrderService.Domain.Exceptions;
-using Xunit;
-
-namespace OrderService.UnitTests.Domain;
-
-///
-/// EN: Unit tests for Sample aggregate.
-/// VI: Unit tests cho Sample aggregate.
-///
-public class SampleAggregateTests
-{
- [Fact]
- public void CreateSample_WithValidName_ShouldCreateWithDraftStatus()
- {
- // Arrange
- var name = "Test Sample";
- var description = "Test Description";
-
- // Act
- var sample = new Sample(name, description);
-
- // Assert
- sample.Name.Should().Be(name);
- sample.Description.Should().Be(description);
- sample.Status.Should().Be(SampleStatus.Draft);
- sample.Id.Should().NotBeEmpty();
- sample.DomainEvents.Should().ContainSingle(); // SampleCreatedDomainEvent
- }
-
- [Fact]
- public void CreateSample_WithEmptyName_ShouldThrowException()
- {
- // Arrange
- var name = "";
-
- // Act
- var act = () => new Sample(name);
-
- // Assert
- act.Should().Throw()
- .WithMessage("Sample name cannot be empty");
- }
-
- [Fact]
- public void Activate_WhenDraft_ShouldChangeToActive()
- {
- // Arrange
- var sample = new Sample("Test Sample");
- sample.ClearDomainEvents();
-
- // Act
- sample.Activate();
-
- // Assert
- sample.Status.Should().Be(SampleStatus.Active);
- sample.DomainEvents.Should().ContainSingle(); // SampleStatusChangedDomainEvent
- }
-
- [Fact]
- public void Activate_WhenNotDraft_ShouldThrowException()
- {
- // Arrange
- var sample = new Sample("Test Sample");
- sample.Activate();
-
- // Act
- var act = () => sample.Activate();
-
- // Assert
- act.Should().Throw()
- .WithMessage("Only draft samples can be activated");
- }
-
- [Fact]
- public void Complete_WhenActive_ShouldChangeToCompleted()
- {
- // Arrange
- var sample = new Sample("Test Sample");
- sample.Activate();
- sample.ClearDomainEvents();
-
- // Act
- sample.Complete();
-
- // Assert
- sample.Status.Should().Be(SampleStatus.Completed);
- }
-
- [Fact]
- public void Cancel_WhenDraftOrActive_ShouldChangeToCancelled()
- {
- // Arrange
- var sample = new Sample("Test Sample");
-
- // Act
- sample.Cancel();
-
- // Assert
- sample.Status.Should().Be(SampleStatus.Cancelled);
- }
-
- [Fact]
- public void Cancel_WhenCompleted_ShouldThrowException()
- {
- // Arrange
- var sample = new Sample("Test Sample");
- sample.Activate();
- sample.Complete();
-
- // Act
- var act = () => sample.Cancel();
-
- // Assert
- act.Should().Throw()
- .WithMessage("Cannot cancel a completed sample");
- }
-
- [Fact]
- public void Update_WhenNotCancelled_ShouldUpdateNameAndDescription()
- {
- // Arrange
- var sample = new Sample("Original Name", "Original Description");
- var newName = "Updated Name";
- var newDescription = "Updated Description";
-
- // Act
- sample.Update(newName, newDescription);
-
- // Assert
- sample.Name.Should().Be(newName);
- sample.Description.Should().Be(newDescription);
- sample.UpdatedAt.Should().NotBeNull();
- }
-
- [Fact]
- public void Update_WhenCancelled_ShouldThrowException()
- {
- // Arrange
- var sample = new Sample("Test Sample");
- sample.Cancel();
-
- // Act
- var act = () => sample.Update("New Name", null);
-
- // Assert
- act.Should().Throw()
- .WithMessage("Cannot update a cancelled sample");
- }
-}