feat(restaurant-admin-bff): add admin workflow endpoints
This commit is contained in:
parent
4c61e61925
commit
8029c42b48
@ -4,4 +4,7 @@
|
||||
<Project Path="src/Restaurant.Admin.Bff.Contracts/Restaurant.Admin.Bff.Contracts.csproj" />
|
||||
<Project Path="src/Restaurant.Admin.Bff.Rest/Restaurant.Admin.Bff.Rest.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/Restaurant.Admin.Bff.Application.UnitTests/Restaurant.Admin.Bff.Application.UnitTests.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
|
||||
27
docs/api/restaurant-admin-workflows.md
Normal file
27
docs/api/restaurant-admin-workflows.md
Normal file
@ -0,0 +1,27 @@
|
||||
# Restaurant Admin Workflow API
|
||||
|
||||
## Purpose
|
||||
|
||||
This BFF exposes restaurant-admin control-plane workflows over REST while delegating orchestration to `operations-service`.
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `GET /api/restaurant/admin/config?contextId=<value>`
|
||||
- Returns configuration snapshot, feature flags, service windows, and recent changes.
|
||||
- `GET /api/restaurant/admin/changes?contextId=<value>`
|
||||
- Returns recent configuration change history.
|
||||
- `POST /api/restaurant/admin/service-window`
|
||||
- Updates a service window and returns the applied snapshot.
|
||||
|
||||
## Upstream Dependency
|
||||
|
||||
- Base address configuration: `OperationsService:BaseAddress`
|
||||
- Default runtime target: `http://operations-service:8080`
|
||||
- Internal upstream routes:
|
||||
- `GET /internal/operations/admin/config`
|
||||
- `POST /internal/operations/admin/service-window`
|
||||
|
||||
## Notes
|
||||
|
||||
- The explicit change-history route is projected from the same admin config snapshot returned by `operations-service`.
|
||||
- Correlation IDs are preserved through Thalos session checks and operations-service calls.
|
||||
@ -1,6 +1,6 @@
|
||||
# Restaurant Admin Ownership Boundary
|
||||
|
||||
- Control-plane-facing BFF for policy, schedule, and operational configuration.
|
||||
- Owns admin edge contracts for feature flags, service windows, and overrides.
|
||||
- Owns admin edge contracts for feature flags, service windows, overrides, and recent change visibility.
|
||||
- Does not own transactional order execution.
|
||||
- Consumes service APIs only; no direct DAL access.
|
||||
|
||||
@ -13,6 +13,7 @@ restaurant-admin-bff
|
||||
- Kitchen queue and dispatch optimization hooks.
|
||||
- Operations control-plane policies (flags, service windows, overrides).
|
||||
- POS closeout and settlement summary alignment.
|
||||
- Admin change history and configuration snapshot workflows that stay aligned with operations-service.
|
||||
|
||||
## Documentation Contract
|
||||
Any code change in this repository must include docs updates in the same branch.
|
||||
|
||||
@ -22,7 +22,8 @@ docker run --rm -p 8080:8080 --name restaurant-admin-bff agilewebs/restaurant-ad
|
||||
|
||||
## Runtime Notes
|
||||
|
||||
- Exposes REST control-plane endpoints for admin configuration updates.
|
||||
- Exposes REST control-plane endpoints for admin configuration snapshots, recent change history, and service-window updates.
|
||||
- Requires `OperationsService__BaseAddress` to resolve the upstream operations-service runtime.
|
||||
- Requires `ThalosAuth__BaseAddress` to resolve Thalos session introspection endpoint.
|
||||
- Returns standardized auth failures (`401|403|503`) with `x-correlation-id` propagation.
|
||||
|
||||
@ -38,5 +39,5 @@ docker run --rm -p 8080:8080 --name restaurant-admin-bff agilewebs/restaurant-ad
|
||||
- Integration artifact path: `greenfield/demo/restaurant/docker-compose.yml`
|
||||
## Known Limitations
|
||||
|
||||
- Current runtime adapters are still predominantly in-memory for deterministic local/demo behavior.
|
||||
- Restaurant-admin now delegates control-plane snapshots to `operations-service`, but the upstream operations adapter still serves deterministic demo data rather than database-backed state.
|
||||
- Demo PostgreSQL seeds validate integration contracts and smoke determinism, but do not yet imply full persistence implementation parity.
|
||||
|
||||
@ -7,6 +7,7 @@ This BFF enforces authenticated access on business endpoints using Thalos sessio
|
||||
## Protected Endpoints
|
||||
|
||||
- `/api/restaurant/admin/config`
|
||||
- `/api/restaurant/admin/changes`
|
||||
- `/api/restaurant/admin/service-window`
|
||||
|
||||
## Anonymous Endpoints
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
using Restaurant.Admin.Bff.Contracts.Requests;
|
||||
using Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Application.Adapters;
|
||||
|
||||
public sealed class DefaultRestaurantAdminServiceClient : IRestaurantAdminServiceClient
|
||||
{
|
||||
public Task<GetRestaurantAdminConfigResponse> FetchAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new GetRestaurantAdminConfigResponse(request.ContextId, "Default service-backed response."));
|
||||
}
|
||||
|
||||
public Task<SetServiceWindowResponse> SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new SetServiceWindowResponse(request.ContextId, true, "Service window updated by default adapter."));
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ namespace Restaurant.Admin.Bff.Application.Adapters;
|
||||
|
||||
public interface IRestaurantAdminServiceClient
|
||||
{
|
||||
Task<GetRestaurantAdminConfigResponse> FetchAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
|
||||
Task<GetRestaurantAdminConfigResponse> FetchConfigAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
|
||||
Task<GetRecentAdminChangesResponse> FetchRecentChangesAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
|
||||
Task<SetServiceWindowResponse> SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
using System.Net.Http.Json;
|
||||
using Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
using Restaurant.Admin.Bff.Contracts.Requests;
|
||||
using Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Application.Adapters;
|
||||
|
||||
public sealed class OperationsRestaurantAdminServiceClient(HttpClient httpClient) : IRestaurantAdminServiceClient
|
||||
{
|
||||
public async Task<GetRestaurantAdminConfigResponse> FetchConfigAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var payload = await GetConfigPayloadAsync(request.ContextId, cancellationToken);
|
||||
|
||||
return new GetRestaurantAdminConfigResponse(
|
||||
payload.ContextId,
|
||||
payload.Summary,
|
||||
payload.Version,
|
||||
payload.FeatureFlags
|
||||
.Select(static flag => new FeatureFlagStateContract(flag.Key, flag.Enabled))
|
||||
.ToArray(),
|
||||
payload.ServiceWindows.Select(MapServiceWindow).ToArray(),
|
||||
payload.RecentChanges.Select(MapConfigChange).ToArray());
|
||||
}
|
||||
|
||||
public async Task<GetRecentAdminChangesResponse> FetchRecentChangesAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var payload = await GetConfigPayloadAsync(request.ContextId, cancellationToken);
|
||||
|
||||
return new GetRecentAdminChangesResponse(
|
||||
payload.ContextId,
|
||||
payload.Summary,
|
||||
payload.Version,
|
||||
payload.RecentChanges.Select(MapConfigChange).ToArray());
|
||||
}
|
||||
|
||||
public async Task<SetServiceWindowResponse> SetServiceWindowAsync(SetServiceWindowRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var payload = await SetServiceWindowPayloadAsync(
|
||||
new SetServiceWindowPayload(
|
||||
request.ContextId,
|
||||
(int)request.Day,
|
||||
request.OpenAt.ToString("HH:mm:ss"),
|
||||
request.CloseAt.ToString("HH:mm:ss"),
|
||||
request.UpdatedBy),
|
||||
cancellationToken);
|
||||
|
||||
return new SetServiceWindowResponse(
|
||||
payload.ContextId,
|
||||
payload.Applied,
|
||||
payload.Message,
|
||||
MapServiceWindow(payload.ServiceWindow));
|
||||
}
|
||||
|
||||
private async Task<GetRestaurantAdminConfigPayload> GetConfigPayloadAsync(string contextId, CancellationToken cancellationToken)
|
||||
{
|
||||
var payload = await httpClient.GetFromJsonAsync<GetRestaurantAdminConfigPayload>(
|
||||
$"internal/operations/admin/config?contextId={Uri.EscapeDataString(contextId)}",
|
||||
cancellationToken);
|
||||
|
||||
return payload ?? throw new InvalidOperationException("Operations service returned an empty restaurant admin config payload.");
|
||||
}
|
||||
|
||||
private async Task<SetServiceWindowResponsePayload> SetServiceWindowPayloadAsync(
|
||||
SetServiceWindowPayload request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using var response = await httpClient.PostAsJsonAsync("internal/operations/admin/service-window", request, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var payload = await response.Content.ReadFromJsonAsync<SetServiceWindowResponsePayload>(cancellationToken);
|
||||
return payload ?? throw new InvalidOperationException("Operations service returned an empty service-window payload.");
|
||||
}
|
||||
|
||||
private static ServiceWindowContract MapServiceWindow(ServiceWindowPayload serviceWindow)
|
||||
{
|
||||
return new ServiceWindowContract(
|
||||
(DayOfWeek)serviceWindow.DayOfWeek,
|
||||
TimeOnly.Parse(serviceWindow.OpenAt),
|
||||
TimeOnly.Parse(serviceWindow.CloseAt),
|
||||
serviceWindow.IsClosed);
|
||||
}
|
||||
|
||||
private static ConfigChangeContract MapConfigChange(ConfigChangePayload change)
|
||||
{
|
||||
return new ConfigChangeContract(
|
||||
change.ChangeId,
|
||||
change.Category,
|
||||
change.Description,
|
||||
change.UpdatedBy,
|
||||
change.UpdatedAtUtc);
|
||||
}
|
||||
|
||||
private sealed record GetRestaurantAdminConfigPayload(
|
||||
string ContextId,
|
||||
string Summary,
|
||||
string Version,
|
||||
IReadOnlyCollection<FeatureFlagPayload> FeatureFlags,
|
||||
IReadOnlyCollection<ServiceWindowPayload> ServiceWindows,
|
||||
IReadOnlyCollection<ConfigChangePayload> RecentChanges);
|
||||
|
||||
private sealed record FeatureFlagPayload(string Key, bool Enabled);
|
||||
|
||||
private sealed record ServiceWindowPayload(
|
||||
int DayOfWeek,
|
||||
string OpenAt,
|
||||
string CloseAt,
|
||||
bool IsClosed);
|
||||
|
||||
private sealed record ConfigChangePayload(
|
||||
string ChangeId,
|
||||
string Category,
|
||||
string Description,
|
||||
string UpdatedBy,
|
||||
DateTime UpdatedAtUtc);
|
||||
|
||||
private sealed record SetServiceWindowPayload(
|
||||
string ContextId,
|
||||
int DayOfWeek,
|
||||
string OpenAt,
|
||||
string CloseAt,
|
||||
string UpdatedBy);
|
||||
|
||||
private sealed record SetServiceWindowResponsePayload(
|
||||
string ContextId,
|
||||
bool Applied,
|
||||
string Message,
|
||||
ServiceWindowPayload ServiceWindow);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using Restaurant.Admin.Bff.Application.Adapters;
|
||||
using Restaurant.Admin.Bff.Contracts.Requests;
|
||||
using Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Application.Handlers;
|
||||
|
||||
public sealed class GetRecentAdminChangesHandler(IRestaurantAdminServiceClient serviceClient) : IGetRecentAdminChangesHandler
|
||||
{
|
||||
public Task<GetRecentAdminChangesResponse> HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return serviceClient.FetchRecentChangesAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,6 @@ public sealed class GetRestaurantAdminConfigHandler(IRestaurantAdminServiceClien
|
||||
{
|
||||
public Task<GetRestaurantAdminConfigResponse> HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return serviceClient.FetchAsync(request, cancellationToken);
|
||||
return serviceClient.FetchConfigAsync(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
using Restaurant.Admin.Bff.Contracts.Requests;
|
||||
using Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Application.Handlers;
|
||||
|
||||
public interface IGetRecentAdminChangesHandler
|
||||
{
|
||||
Task<GetRecentAdminChangesResponse> HandleAsync(GetRestaurantAdminConfigRequest request, CancellationToken cancellationToken);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
public sealed record ConfigChangeContract(
|
||||
string ChangeId,
|
||||
string Category,
|
||||
string Description,
|
||||
string UpdatedBy,
|
||||
DateTime UpdatedAtUtc);
|
||||
@ -0,0 +1,3 @@
|
||||
namespace Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
public sealed record FeatureFlagStateContract(string Key, bool Enabled);
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
public sealed record ServiceWindowContract(
|
||||
DayOfWeek Day,
|
||||
TimeOnly OpenAt,
|
||||
TimeOnly CloseAt,
|
||||
bool IsClosed);
|
||||
@ -0,0 +1,9 @@
|
||||
using Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
public sealed record GetRecentAdminChangesResponse(
|
||||
string ContextId,
|
||||
string Summary,
|
||||
string Version,
|
||||
IReadOnlyCollection<ConfigChangeContract> RecentChanges);
|
||||
@ -1,3 +1,11 @@
|
||||
using Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
public sealed record GetRestaurantAdminConfigResponse(string ContextId, string Summary);
|
||||
public sealed record GetRestaurantAdminConfigResponse(
|
||||
string ContextId,
|
||||
string Summary,
|
||||
string Version,
|
||||
IReadOnlyCollection<FeatureFlagStateContract> FeatureFlags,
|
||||
IReadOnlyCollection<ServiceWindowContract> ServiceWindows,
|
||||
IReadOnlyCollection<ConfigChangeContract> RecentChanges);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
using Restaurant.Admin.Bff.Contracts.Contracts;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Contracts.Responses;
|
||||
|
||||
public sealed record SetServiceWindowResponse(
|
||||
string ContextId,
|
||||
bool Applied,
|
||||
string Message);
|
||||
string Message,
|
||||
ServiceWindowContract ServiceWindow);
|
||||
|
||||
@ -10,8 +10,14 @@ const string SessionAccessCookieName = "thalos_session";
|
||||
const string SessionRefreshCookieName = "thalos_refresh";
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddSingleton<IRestaurantAdminServiceClient, DefaultRestaurantAdminServiceClient>();
|
||||
builder.Services.AddHttpClient<IRestaurantAdminServiceClient, OperationsRestaurantAdminServiceClient>((serviceProvider, httpClient) =>
|
||||
{
|
||||
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
|
||||
var operationsBaseAddress = configuration["OperationsService:BaseAddress"] ?? "http://operations-service:8080";
|
||||
httpClient.BaseAddress = new Uri($"{operationsBaseAddress.TrimEnd('/')}/");
|
||||
});
|
||||
builder.Services.AddSingleton<IGetRestaurantAdminConfigHandler, GetRestaurantAdminConfigHandler>();
|
||||
builder.Services.AddSingleton<IGetRecentAdminChangesHandler, GetRecentAdminChangesHandler>();
|
||||
builder.Services.AddSingleton<ISetServiceWindowHandler, SetServiceWindowHandler>();
|
||||
builder.Services.AddHttpClient("ThalosAuth");
|
||||
|
||||
@ -44,6 +50,24 @@ app.MapGet("/api/restaurant/admin/config", async (
|
||||
return Results.Ok(await handler.HandleAsync(request, ct));
|
||||
});
|
||||
|
||||
app.MapGet("/api/restaurant/admin/changes", async (
|
||||
string contextId,
|
||||
HttpContext context,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IConfiguration configuration,
|
||||
IGetRecentAdminChangesHandler handler,
|
||||
CancellationToken ct) =>
|
||||
{
|
||||
var authError = await EnforceSessionAsync(context, httpClientFactory, configuration, ct);
|
||||
if (authError is not null)
|
||||
{
|
||||
return authError;
|
||||
}
|
||||
|
||||
var request = new GetRestaurantAdminConfigRequest(contextId);
|
||||
return Results.Ok(await handler.HandleAsync(request, ct));
|
||||
});
|
||||
|
||||
app.MapPost("/api/restaurant/admin/service-window", async (
|
||||
SetServiceWindowRequest request,
|
||||
HttpContext context,
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Restaurant.Admin.Bff.Application.Adapters;
|
||||
using Restaurant.Admin.Bff.Contracts.Requests;
|
||||
|
||||
namespace Restaurant.Admin.Bff.Application.UnitTests;
|
||||
|
||||
public sealed class OperationsRestaurantAdminServiceClientTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task FetchConfigAsync_MapsFlagsWindowsAndChanges()
|
||||
{
|
||||
var adapter = new OperationsRestaurantAdminServiceClient(CreateClient(ConfigPayload));
|
||||
|
||||
var response = await adapter.FetchConfigAsync(new GetRestaurantAdminConfigRequest("demo-context"), CancellationToken.None);
|
||||
|
||||
Assert.Equal("v2", response.Version);
|
||||
Assert.NotEmpty(response.FeatureFlags);
|
||||
Assert.NotEmpty(response.ServiceWindows);
|
||||
Assert.NotEmpty(response.RecentChanges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchRecentChangesAsync_ProjectsChangeHistory()
|
||||
{
|
||||
var adapter = new OperationsRestaurantAdminServiceClient(CreateClient(ConfigPayload));
|
||||
|
||||
var response = await adapter.FetchRecentChangesAsync(new GetRestaurantAdminConfigRequest("demo-context"), CancellationToken.None);
|
||||
|
||||
Assert.Equal(2, response.RecentChanges.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetServiceWindowAsync_MapsUpdatedServiceWindow()
|
||||
{
|
||||
var adapter = new OperationsRestaurantAdminServiceClient(CreateClient("""
|
||||
{
|
||||
"contextId": "demo-context",
|
||||
"applied": true,
|
||||
"message": "Service window updated by admin-operator.",
|
||||
"serviceWindow": {
|
||||
"dayOfWeek": 1,
|
||||
"openAt": "08:00:00",
|
||||
"closeAt": "22:00:00",
|
||||
"isClosed": false
|
||||
}
|
||||
}
|
||||
"""));
|
||||
|
||||
var response = await adapter.SetServiceWindowAsync(
|
||||
new SetServiceWindowRequest("demo-context", DayOfWeek.Monday, new TimeOnly(8, 0), new TimeOnly(22, 0), "admin-operator"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.True(response.Applied);
|
||||
Assert.Equal(DayOfWeek.Monday, response.ServiceWindow.Day);
|
||||
}
|
||||
|
||||
private static HttpClient CreateClient(string json)
|
||||
{
|
||||
return new HttpClient(new StubHttpMessageHandler(json))
|
||||
{
|
||||
BaseAddress = new Uri("http://operations-service:8080/")
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class StubHttpMessageHandler(string json) : HttpMessageHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private const string ConfigPayload = """
|
||||
{
|
||||
"contextId": "demo-context",
|
||||
"summary": "Restaurant admin snapshot includes current flags, service windows, and recent control-plane changes.",
|
||||
"version": "v2",
|
||||
"featureFlags": [
|
||||
{ "key": "kitchen.dispatch.enabled", "enabled": true },
|
||||
{ "key": "pos.closeout.preview", "enabled": true }
|
||||
],
|
||||
"serviceWindows": [
|
||||
{ "dayOfWeek": 1, "openAt": "08:00:00", "closeAt": "22:00:00", "isClosed": false },
|
||||
{ "dayOfWeek": 5, "openAt": "08:00:00", "closeAt": "23:30:00", "isClosed": false }
|
||||
],
|
||||
"recentChanges": [
|
||||
{
|
||||
"changeId": "CFG-100",
|
||||
"category": "service-window",
|
||||
"description": "Extended Friday dinner service window.",
|
||||
"updatedBy": "admin-operator",
|
||||
"updatedAtUtc": "2026-03-31T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"changeId": "CFG-101",
|
||||
"category": "feature-flag",
|
||||
"description": "Enabled POS closeout preview mode.",
|
||||
"updatedBy": "ops-lead",
|
||||
"updatedAtUtc": "2026-03-31T12:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../src/Restaurant.Admin.Bff.Application/Restaurant.Admin.Bff.Application.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue
Block a user