test: replace sample tests with service-specific order and ads suites

Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-02-23 12:47:21 +00:00
parent 9e5b1018b4
commit 0f828dafb0
8 changed files with 368 additions and 391 deletions

View File

@@ -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;
/// <summary>
/// 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.
/// </summary>
public class AdsBillingControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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<Guid>();
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<JsonElement>();
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<JsonElement>();
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<Guid>();
// 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);
}
}

View File

@@ -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;
/// <summary>
/// EN: Functional tests for Samples API endpoints.
/// VI: Functional tests cho các endpoints API Samples.
/// </summary>
public class SamplesControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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<ApiResponse<List<object>>>();
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<ApiResponse<CreateSampleResult>>();
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<T>(bool Success, T? Data);
private record CreateSampleResult(Guid Id);
}

View File

@@ -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;
/// <summary>
/// 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.
/// </summary>
public class AdsServingControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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<string, string>
{
["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<JsonElement>();
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<string, string>(),
};
// 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<JsonElement>();
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);
}
}

View File

@@ -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;
/// <summary>
/// EN: Functional tests for Samples API endpoints.
/// VI: Functional tests cho các endpoints API Samples.
/// </summary>
public class SamplesControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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<ApiResponse<List<object>>>();
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<ApiResponse<CreateSampleResult>>();
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<T>(bool Success, T? Data);
private record CreateSampleResult(Guid Id);
}

View File

@@ -0,0 +1,33 @@
using System.Net;
using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
namespace OrderService.FunctionalTests.Controllers;
/// <summary>
/// EN: Functional tests for health check endpoints.
/// VI: Functional tests cho các endpoint kiểm tra sức khỏe.
/// </summary>
public class HealthChecksControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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);
}
}

View File

@@ -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;
/// <summary>
/// EN: Functional tests for Samples API endpoints.
/// VI: Functional tests cho các endpoints API Samples.
/// </summary>
public class SamplesControllerTests : IClassFixture<CustomWebApplicationFactory>
{
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<ApiResponse<List<object>>>();
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<ApiResponse<CreateSampleResult>>();
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<T>(bool Success, T? Data);
private record CreateSampleResult(Guid Id);
}

View File

@@ -0,0 +1,97 @@
using FluentAssertions;
using OrderService.Domain.AggregatesModel.OrderAggregate;
using OrderService.Domain.Exceptions;
using Xunit;
namespace OrderService.UnitTests.Domain;
/// <summary>
/// EN: Unit tests for Order aggregate domain behavior.
/// VI: Unit tests cho hành vi domain của aggregate Order.
/// </summary>
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<DomainException>()
.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<DomainException>()
.WithMessage("Cannot cancel completed order");
}
}

View File

@@ -1,151 +0,0 @@
using FluentAssertions;
using OrderService.Domain.AggregatesModel.SampleAggregate;
using OrderService.Domain.Exceptions;
using Xunit;
namespace OrderService.UnitTests.Domain;
/// <summary>
/// EN: Unit tests for Sample aggregate.
/// VI: Unit tests cho Sample aggregate.
/// </summary>
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<SampleDomainException>()
.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<SampleDomainException>()
.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<SampleDomainException>()
.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<SampleDomainException>()
.WithMessage("Cannot update a cancelled sample");
}
}