diff --git a/services/booking-service-net/tests/BookingService.FunctionalTests/Controllers/ResourcesControllerTests.cs b/services/booking-service-net/tests/BookingService.FunctionalTests/Controllers/ResourcesControllerTests.cs
new file mode 100644
index 00000000..1958f56c
--- /dev/null
+++ b/services/booking-service-net/tests/BookingService.FunctionalTests/Controllers/ResourcesControllerTests.cs
@@ -0,0 +1,43 @@
+using System.Net;
+using FluentAssertions;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Xunit;
+
+namespace BookingService.FunctionalTests.Controllers;
+
+///
+/// EN: Functional tests for booking resource endpoints.
+/// VI: Functional tests cho endpoint tài nguyên đặt lịch.
+///
+public class ResourcesControllerTests : IClassFixture
+{
+ private readonly HttpClient _client;
+
+ public ResourcesControllerTests(CustomWebApplicationFactory factory)
+ {
+ _client = factory.CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false,
+ });
+ }
+
+ [Fact]
+ public async Task GetResources_ShouldReturnOk()
+ {
+ // Act
+ var response = await _client.GetAsync($"/api/v1/resources?shopId={Guid.NewGuid()}");
+
+ // Assert
+ response.StatusCode.Should().Be(HttpStatusCode.OK);
+ }
+
+ [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/booking-service-net/tests/BookingService.FunctionalTests/Controllers/SamplesControllerTests.cs b/services/booking-service-net/tests/BookingService.FunctionalTests/Controllers/SamplesControllerTests.cs
deleted file mode 100644
index e5a2bfe6..00000000
--- a/services/booking-service-net/tests/BookingService.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 BookingService.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/booking-service-net/tests/BookingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs b/services/booking-service-net/tests/BookingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs
deleted file mode 100644
index 0e10b430..00000000
--- a/services/booking-service-net/tests/BookingService.UnitTests/Application/CreateSampleCommandHandlerTests.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using FluentAssertions;
-using Microsoft.Extensions.Logging;
-using Moq;
-using BookingService.API.Application.Commands;
-using BookingService.Domain.AggregatesModel.SampleAggregate;
-using BookingService.Domain.SeedWork;
-using Xunit;
-
-namespace BookingService.UnitTests.Application;
-
-///
-/// EN: Unit tests for CreateSampleCommandHandler.
-/// VI: Unit tests cho CreateSampleCommandHandler.
-///
-public class CreateSampleCommandHandlerTests
-{
- private readonly Mock _mockRepository;
- private readonly Mock> _mockLogger;
- private readonly CreateSampleCommandHandler _handler;
-
- public CreateSampleCommandHandlerTests()
- {
- _mockRepository = new Mock();
- _mockLogger = new Mock>();
-
- var mockUnitOfWork = new Mock();
- mockUnitOfWork.Setup(u => u.SaveEntitiesAsync(It.IsAny()))
- .ReturnsAsync(true);
-
- _mockRepository.SetupGet(r => r.UnitOfWork).Returns(mockUnitOfWork.Object);
-
- _handler = new CreateSampleCommandHandler(_mockRepository.Object, _mockLogger.Object);
- }
-
- [Fact]
- public async Task Handle_WithValidCommand_ShouldCreateSampleAndReturnId()
- {
- // Arrange
- var command = new CreateSampleCommand("Test Sample", "Test Description");
-
- _mockRepository.Setup(r => r.Add(It.IsAny()))
- .Returns((Sample s) => s);
-
- // Act
- var result = await _handler.Handle(command, CancellationToken.None);
-
- // Assert
- result.Should().NotBeNull();
- result.Id.Should().NotBeEmpty();
- _mockRepository.Verify(r => r.Add(It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task Handle_WithValidCommand_ShouldCallSaveEntities()
- {
- // Arrange
- var command = new CreateSampleCommand("Test Sample", null);
-
- // Act
- await _handler.Handle(command, CancellationToken.None);
-
- // Assert
- _mockRepository.Verify(r => r.UnitOfWork.SaveEntitiesAsync(It.IsAny()), Times.Once);
- }
-}
diff --git a/services/booking-service-net/tests/BookingService.UnitTests/Domain/AppointmentAggregateTests.cs b/services/booking-service-net/tests/BookingService.UnitTests/Domain/AppointmentAggregateTests.cs
new file mode 100644
index 00000000..8fdb95c9
--- /dev/null
+++ b/services/booking-service-net/tests/BookingService.UnitTests/Domain/AppointmentAggregateTests.cs
@@ -0,0 +1,68 @@
+using FluentAssertions;
+using BookingService.Domain.AggregatesModel.AppointmentAggregate;
+using BookingService.Domain.Exceptions;
+using Xunit;
+
+namespace BookingService.UnitTests.Domain;
+
+///
+/// EN: Unit tests for appointment aggregate behavior.
+/// VI: Unit tests cho hành vi aggregate cuộc hẹn.
+///
+public class AppointmentAggregateTests
+{
+ [Fact]
+ public void CreateAppointment_ShouldInitializeAsScheduled()
+ {
+ // Act
+ var appointment = new Appointment(
+ Guid.NewGuid(),
+ Guid.NewGuid(),
+ DateTime.UtcNow.AddHours(1),
+ DateTime.UtcNow.AddHours(2),
+ Guid.NewGuid());
+
+ // Assert
+ appointment.Status.Should().Be("Scheduled");
+ appointment.Id.Should().NotBeEmpty();
+ }
+
+ [Fact]
+ public void Complete_AfterConfirmAndStart_ShouldSetCompleted()
+ {
+ // Arrange
+ var appointment = new Appointment(
+ Guid.NewGuid(),
+ Guid.NewGuid(),
+ DateTime.UtcNow.AddHours(2),
+ DateTime.UtcNow.AddHours(3));
+ appointment.Confirm();
+ appointment.MarkAsInProgress();
+
+ // Act
+ appointment.Complete();
+
+ // Assert
+ appointment.Status.Should().Be("Completed");
+ }
+
+ [Fact]
+ public void Cancel_WhenCompleted_ShouldThrowDomainException()
+ {
+ // Arrange
+ var appointment = new Appointment(
+ Guid.NewGuid(),
+ Guid.NewGuid(),
+ DateTime.UtcNow.AddHours(2),
+ DateTime.UtcNow.AddHours(3));
+ appointment.Confirm();
+ appointment.MarkAsInProgress();
+ appointment.Complete();
+
+ // Act
+ var action = () => appointment.Cancel("Customer requested");
+
+ // Assert
+ action.Should().Throw();
+ }
+}
diff --git a/services/booking-service-net/tests/BookingService.UnitTests/Domain/SampleAggregateTests.cs b/services/booking-service-net/tests/BookingService.UnitTests/Domain/SampleAggregateTests.cs
deleted file mode 100644
index 3b3081d4..00000000
--- a/services/booking-service-net/tests/BookingService.UnitTests/Domain/SampleAggregateTests.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-using FluentAssertions;
-using BookingService.Domain.AggregatesModel.SampleAggregate;
-using BookingService.Domain.Exceptions;
-using Xunit;
-
-namespace BookingService.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");
- }
-}