From 0f828dafb05c7ef98774b2ee72604b7cb2e3b633 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 23 Feb 2026 12:47:21 +0000 Subject: [PATCH] test: replace sample tests with service-specific order and ads suites Co-authored-by: Velik --- .../Controllers/AdsBillingControllerTests.cs | 133 +++++++++++++++ .../Controllers/SamplesControllerTests.cs | 80 ---------- .../Controllers/AdsServingControllerTests.cs | 105 ++++++++++++ .../Controllers/SamplesControllerTests.cs | 80 ---------- .../HealthChecksControllerTests.cs | 33 ++++ .../Controllers/SamplesControllerTests.cs | 80 ---------- .../Domain/OrderAggregateTests.cs | 97 +++++++++++ .../Domain/SampleAggregateTests.cs | 151 ------------------ 8 files changed, 368 insertions(+), 391 deletions(-) create mode 100644 services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/AdsBillingControllerTests.cs delete mode 100644 services/ads-billing-service-net/tests/AdsBillingService.FunctionalTests/Controllers/SamplesControllerTests.cs create mode 100644 services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/AdsServingControllerTests.cs delete mode 100644 services/ads-serving-service-net/tests/AdsServingService.FunctionalTests/Controllers/SamplesControllerTests.cs create mode 100644 services/order-service-net/tests/OrderService.FunctionalTests/Controllers/HealthChecksControllerTests.cs delete mode 100644 services/order-service-net/tests/OrderService.FunctionalTests/Controllers/SamplesControllerTests.cs create mode 100644 services/order-service-net/tests/OrderService.UnitTests/Domain/OrderAggregateTests.cs delete mode 100644 services/order-service-net/tests/OrderService.UnitTests/Domain/SampleAggregateTests.cs 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"); - } -}