using System.Net;
using System.Text;
using FluentAssertions;
using WebClientTpos.Client.Services;
using Xunit;
namespace WebClientTpos.ComponentTests.Services;
///
/// EN: Unit tests for PosDataService — smart 4-format JSON deserialization and HTTP helpers.
/// VI: Unit tests cho PosDataService — deserialize JSON thông minh 4 định dạng và các HTTP helper.
///
public class PosDataServiceTests
{
// EN: Simple item DTO used across tests.
// VI: DTO item đơn giản dùng trong các test.
private record ItemDto(Guid Id, string Name);
// -----------------------------------------------------------------------
// Helper — build PosDataService with a mocked HttpMessageHandler
// -----------------------------------------------------------------------
private static (PosDataService sut, AuthStateService authState) BuildSut(
string responseBody,
HttpStatusCode statusCode = HttpStatusCode.OK)
{
// EN: Auth is via BFF httpOnly cookie — AuthStateService only tracks email/role/expiry.
// VI: Auth qua BFF httpOnly cookie — AuthStateService chỉ theo dõi email/role/expiry.
var authState = new AuthStateService();
authState.Login("test@goodgo.vn", "owner");
var handler = new FakeHttpMessageHandler(responseBody, statusCode);
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost/") };
var sut = new PosDataService(httpClient);
return (sut, authState);
}
// -----------------------------------------------------------------------
// GetListFromApiAsync — 4-format deserialization
// -----------------------------------------------------------------------
[Fact]
public async Task GetListFromApiAsync_PlainArray_ShouldDeserializeCorrectly()
{
// Arrange — Case 1: plain JSON array
var json = """
[
{"id":"00000000-0000-0000-0000-000000000001","name":"Espresso"},
{"id":"00000000-0000-0000-0000-000000000002","name":"Cappuccino"}
]
""";
var (sut, _) = BuildSut(json);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().HaveCount(2);
result[0].Name.Should().Be("Espresso");
}
[Fact]
public async Task GetListFromApiAsync_PagedResultWrapper_ShouldDeserializeCorrectly()
{
// Arrange — Case 2: { "items": [...] }
var json = """
{
"items": [
{"id":"00000000-0000-0000-0000-000000000003","name":"Latte"},
{"id":"00000000-0000-0000-0000-000000000004","name":"Mocha"}
],
"totalCount": 2,
"pageNumber": 1,
"pageSize": 10
}
""";
var (sut, _) = BuildSut(json);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().HaveCount(2);
result[1].Name.Should().Be("Mocha");
}
[Fact]
public async Task GetListFromApiAsync_ApiResponseWithPagedData_ShouldDeserializeCorrectly()
{
// Arrange — Case 3: { "data": { "items": [...] } }
var json = """
{
"success": true,
"data": {
"items": [
{"id":"00000000-0000-0000-0000-000000000005","name":"Flat White"}
],
"totalCount": 1
}
}
""";
var (sut, _) = BuildSut(json);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().HaveCount(1);
result[0].Name.Should().Be("Flat White");
}
[Fact]
public async Task GetListFromApiAsync_ApiResponseWithDirectArray_ShouldDeserializeCorrectly()
{
// Arrange — Case 4: { "data": [...] }
var json = """
{
"success": true,
"data": [
{"id":"00000000-0000-0000-0000-000000000006","name":"Americano"}
]
}
""";
var (sut, _) = BuildSut(json);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().HaveCount(1);
result[0].Name.Should().Be("Americano");
}
[Fact]
public async Task GetListFromApiAsync_GenericWrapper_ShouldDeserializeFirstArrayProperty()
{
// Arrange — Case 5: { "products": [...] } (generic single-array-property)
var json = """
{
"products": [
{"id":"00000000-0000-0000-0000-000000000007","name":"Cold Brew"}
]
}
""";
var (sut, _) = BuildSut(json);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().HaveCount(1);
result[0].Name.Should().Be("Cold Brew");
}
[Fact]
public async Task GetListFromApiAsync_EmptyJson_ShouldReturnEmptyList()
{
// Arrange
var (sut, _) = BuildSut(" ");
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().BeEmpty();
}
[Fact]
public async Task GetListFromApiAsync_NonSuccessStatus_ShouldReturnEmptyList()
{
// Arrange
var (sut, _) = BuildSut("{}", HttpStatusCode.InternalServerError);
// Act
var result = await sut.GetListFromApiAsync("api/bff/products");
// Assert
result.Should().BeEmpty();
}
// -----------------------------------------------------------------------
// PostAsync
// -----------------------------------------------------------------------
[Fact]
public async Task PostAsync_OnSuccess_ShouldReturnTrue()
{
// Arrange
var (sut, _) = BuildSut("{\"success\":true}", HttpStatusCode.OK);
// Act
var result = await sut.PostAsync("api/bff/orders", new { quantity = 1 });
// Assert
result.Should().BeTrue();
}
[Fact]
public async Task PostAsync_OnBadRequest_ShouldReturnFalse()
{
// Arrange
var (sut, _) = BuildSut("{\"success\":false}", HttpStatusCode.BadRequest);
// Act
var result = await sut.PostAsync("api/bff/orders", new { quantity = -1 });
// Assert
result.Should().BeFalse();
}
// -----------------------------------------------------------------------
// PutAsync
// -----------------------------------------------------------------------
[Fact]
public async Task PutAsync_OnSuccess_ShouldReturnTrue()
{
var (sut, _) = BuildSut("{}", HttpStatusCode.OK);
var result = await sut.PutAsync("api/bff/products/1", new { price = 40000 });
result.Should().BeTrue();
}
[Fact]
public async Task PutAsync_OnNotFound_ShouldReturnFalse()
{
var (sut, _) = BuildSut("{}", HttpStatusCode.NotFound);
var result = await sut.PutAsync("api/bff/products/nonexistent", new { price = 0 });
result.Should().BeFalse();
}
}
// ---------------------------------------------------------------------------
// Test helper — FakeHttpMessageHandler
// ---------------------------------------------------------------------------
///
/// EN: In-memory HTTP handler for unit testing HttpClient-based services.
/// VI: HTTP handler in-memory để unit test các service dùng HttpClient.
///
internal class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly string _responseBody;
private readonly HttpStatusCode _statusCode;
public FakeHttpMessageHandler(string responseBody, HttpStatusCode statusCode = HttpStatusCode.OK)
{
_responseBody = responseBody;
_statusCode = statusCode;
}
protected override Task SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(_statusCode)
{
Content = new StringContent(_responseBody, Encoding.UTF8, "application/json"),
};
return Task.FromResult(response);
}
}