178 lines
6.3 KiB
C#
178 lines
6.3 KiB
C#
using System.Net;
|
|
using System.Text;
|
|
using Customer.Orders.Bff.Application.Adapters;
|
|
using Customer.Orders.Bff.Contracts.Requests;
|
|
|
|
namespace Customer.Orders.Bff.Application.UnitTests;
|
|
|
|
public sealed class OperationsCustomerOrdersServiceClientTests
|
|
{
|
|
[Fact]
|
|
public async Task FetchStatusAsync_MapsOrdersAndEvents()
|
|
{
|
|
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(new Dictionary<string, string>
|
|
{
|
|
["/internal/operations/customer/status"] = StatusPayload
|
|
}));
|
|
|
|
var response = await adapter.FetchStatusAsync(new GetCustomerOrderStatusRequest("demo-context"), CancellationToken.None);
|
|
|
|
Assert.Equal("demo-context", response.ContextId);
|
|
Assert.NotEmpty(response.Orders);
|
|
Assert.NotEmpty(response.RecentEvents);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FetchDetailAsync_ReturnsMatchingOrderWhenPresent()
|
|
{
|
|
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(new Dictionary<string, string>
|
|
{
|
|
["/internal/operations/customer/orders/ORD-1001"] = DetailPayload
|
|
}));
|
|
|
|
var response = await adapter.FetchDetailAsync(new GetCustomerOrderDetailRequest("demo-context", "ORD-1001"), CancellationToken.None);
|
|
|
|
Assert.NotNull(response.Order);
|
|
Assert.Equal("ORD-1001", response.Order!.OrderId);
|
|
Assert.Contains("preparing", response.Summary, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FetchHistoryAsync_ReturnsLifecycleBackedHistorySnapshot()
|
|
{
|
|
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(new Dictionary<string, string>
|
|
{
|
|
["/internal/operations/customer/history"] = HistoryPayload
|
|
}));
|
|
|
|
var response = await adapter.FetchHistoryAsync(new GetCustomerOrderStatusRequest("demo-context"), CancellationToken.None);
|
|
|
|
Assert.Equal(2, response.Orders.Count);
|
|
Assert.Equal(2, response.RecentEvents.Count);
|
|
Assert.Contains("lifecycle-backed", response.Summary, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitOrderAsync_MapsSharedLifecycleAcceptanceUsingItemCount()
|
|
{
|
|
var adapter = new OperationsCustomerOrdersServiceClient(CreateClient(new Dictionary<string, string>
|
|
{
|
|
["/internal/operations/orders"] = """
|
|
{
|
|
"contextId": "demo-context",
|
|
"orderId": "ORD-1009",
|
|
"accepted": true,
|
|
"summary": "Order ORD-1009 was accepted and is ready for kitchen dispatch.",
|
|
"status": "accepted",
|
|
"submittedAtUtc": "2026-03-31T12:30:00Z"
|
|
}
|
|
"""
|
|
}));
|
|
|
|
var response = await adapter.SubmitOrderAsync(
|
|
new SubmitCustomerOrderRequest("demo-context", "ORD-1009", "T-18", 4, ["ITEM-101", "ITEM-202", "ITEM-303"]),
|
|
CancellationToken.None);
|
|
|
|
Assert.True(response.Accepted);
|
|
Assert.Equal("accepted", response.Status);
|
|
Assert.Contains("kitchen dispatch", response.Summary);
|
|
}
|
|
|
|
private static HttpClient CreateClient(IReadOnlyDictionary<string, string> routes)
|
|
{
|
|
return new HttpClient(new StubHttpMessageHandler(routes))
|
|
{
|
|
BaseAddress = new Uri("http://operations-service:8080/")
|
|
};
|
|
}
|
|
|
|
private sealed class StubHttpMessageHandler(IReadOnlyDictionary<string, string> routes) : HttpMessageHandler
|
|
{
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
var path = request.RequestUri?.AbsolutePath ?? string.Empty;
|
|
if (!routes.TryGetValue(path, out var json))
|
|
{
|
|
throw new InvalidOperationException($"No stub payload configured for '{path}'.");
|
|
}
|
|
|
|
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
|
});
|
|
}
|
|
}
|
|
|
|
private const string StatusPayload = """
|
|
{
|
|
"contextId": "demo-context",
|
|
"summary": "2 customer orders are currently tracked in the shared restaurant lifecycle.",
|
|
"orders": [
|
|
{
|
|
"orderId": "ORD-1001",
|
|
"tableId": "T-08",
|
|
"status": "preparing",
|
|
"guestCount": 2,
|
|
"itemIds": [ "ITEM-101", "ITEM-202" ]
|
|
},
|
|
{
|
|
"orderId": "ORD-1002",
|
|
"tableId": "T-15",
|
|
"status": "ready",
|
|
"guestCount": 4,
|
|
"itemIds": [ "ITEM-301", "ITEM-404", "ITEM-405" ]
|
|
}
|
|
],
|
|
"recentEvents": [
|
|
"Order ORD-1001 moved to preparing at the kitchen hot-line station.",
|
|
"Order ORD-1002 is ready for table pickup."
|
|
]
|
|
}
|
|
""";
|
|
|
|
private const string DetailPayload = """
|
|
{
|
|
"contextId": "demo-context",
|
|
"summary": "Order ORD-1001 is currently preparing in the shared restaurant lifecycle.",
|
|
"order": {
|
|
"orderId": "ORD-1001",
|
|
"tableId": "T-08",
|
|
"status": "preparing",
|
|
"guestCount": 2,
|
|
"itemIds": [ "ITEM-101", "ITEM-202" ]
|
|
},
|
|
"recentEvents": [
|
|
"Order ORD-1001 was accepted into the shared restaurant lifecycle.",
|
|
"Order ORD-1001 moved to preparing at the kitchen hot-line station."
|
|
]
|
|
}
|
|
""";
|
|
|
|
private const string HistoryPayload = """
|
|
{
|
|
"contextId": "demo-context",
|
|
"summary": "2 customer orders are currently available through lifecycle-backed history.",
|
|
"orders": [
|
|
{
|
|
"orderId": "ORD-1001",
|
|
"tableId": "T-08",
|
|
"status": "preparing",
|
|
"guestCount": 2,
|
|
"itemIds": [ "ITEM-101", "ITEM-202" ]
|
|
},
|
|
{
|
|
"orderId": "ORD-1002",
|
|
"tableId": "T-15",
|
|
"status": "ready",
|
|
"guestCount": 4,
|
|
"itemIds": [ "ITEM-301", "ITEM-404", "ITEM-405" ]
|
|
}
|
|
],
|
|
"recentEvents": [
|
|
"Order ORD-1001 moved to preparing at the kitchen hot-line station.",
|
|
"Order ORD-1002 is ready for table pickup."
|
|
]
|
|
}
|
|
""";
|
|
}
|