feat: initialize manufacturing progress dashboard
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using DongfangHydro.Dashboard.Api.Data;
|
||||
using DongfangHydro.Dashboard.Api.Contracts;
|
||||
using DongfangHydro.Dashboard.Api.Realtime;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace DongfangHydro.Dashboard.Tests;
|
||||
|
||||
public sealed class DashboardApiTests(DashboardApiFactory factory)
|
||||
: IClassFixture<DashboardApiFactory>
|
||||
{
|
||||
[Fact]
|
||||
public async Task Orders_SearchesSeededWaterConservancyProductNames()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.GetAsync("/api/orders?keyword=门架&page=1&pageSize=20");
|
||||
var body = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.True(body.GetProperty("total").GetInt32() >= 1);
|
||||
Assert.Contains(
|
||||
body.GetProperty("items").EnumerateArray(),
|
||||
item => item.GetProperty("productName").GetString()!.Contains("门架", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OrderTree_ReturnsTheCompleteSeededHierarchy()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
var orders = await client.GetFromJsonAsync<JsonElement>("/api/orders?page=1&pageSize=20");
|
||||
var orderId = orders.GetProperty("items")[0].GetProperty("id").GetString();
|
||||
|
||||
var root = await client.GetFromJsonAsync<JsonElement>($"/api/orders/{orderId}/tree");
|
||||
|
||||
Assert.Equal("order", root.GetProperty("nodeType").GetString());
|
||||
Assert.Equal(66, CountNodes(root));
|
||||
Assert.Equal(1, CountNodes(root, "order"));
|
||||
Assert.Equal(5, CountNodes(root, "part"));
|
||||
Assert.Equal(15, CountNodes(root, "component"));
|
||||
Assert.Equal(45, CountNodes(root, "process"));
|
||||
Assert.False(string.IsNullOrWhiteSpace(FindFirstProcess(root).GetProperty("version").GetString()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateProgress_RecalculatesTheOrderAndReturnsTheUpdatedNode()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
factory.UpdateQueue.Reset();
|
||||
var orders = await client.GetFromJsonAsync<JsonElement>("/api/orders?page=1&pageSize=20");
|
||||
var orderId = orders.GetProperty("items")[0].GetProperty("id").GetString();
|
||||
var root = await client.GetFromJsonAsync<JsonElement>($"/api/orders/{orderId}/tree");
|
||||
var process = FindFirstProcess(root);
|
||||
|
||||
var response = await client.PatchAsJsonAsync(
|
||||
$"/api/nodes/{process.GetProperty("id").GetString()}/progress",
|
||||
new
|
||||
{
|
||||
completedQty = process.GetProperty("requiredQty").GetInt32(),
|
||||
expectedVersion = process.GetProperty("version").GetString(),
|
||||
status = "done",
|
||||
riskLevel = "normal",
|
||||
delayDays = 0,
|
||||
delayReason = string.Empty,
|
||||
});
|
||||
var updated = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(100, updated.GetProperty("node").GetProperty("progress").GetInt32());
|
||||
Assert.Equal("done", updated.GetProperty("node").GetProperty("status").GetString());
|
||||
Assert.Equal(orderId, updated.GetProperty("order").GetProperty("id").GetString());
|
||||
Assert.Equal(1, factory.UpdateQueue.EnqueueCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateProgress_RejectsARequestWithoutCompletedQuantity()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
var process = await GetFirstProcessAsync(client);
|
||||
|
||||
var response = await client.PatchAsJsonAsync(
|
||||
$"/api/nodes/{process.GetProperty("id").GetString()}/progress",
|
||||
new { status = "in_progress" });
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateProgress_RejectsDoneStatusBeforeThePlannedQuantityIsComplete()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
var process = await GetFirstProcessAsync(client);
|
||||
var requiredQty = process.GetProperty("requiredQty").GetInt32();
|
||||
|
||||
var response = await client.PatchAsJsonAsync(
|
||||
$"/api/nodes/{process.GetProperty("id").GetString()}/progress",
|
||||
new
|
||||
{
|
||||
completedQty = requiredQty - 1,
|
||||
expectedVersion = process.GetProperty("version").GetString(),
|
||||
status = "done",
|
||||
riskLevel = "normal",
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateProgress_RequiresTheClientsExpectedVersion()
|
||||
{
|
||||
using var client = factory.CreateClient();
|
||||
var process = await GetFirstProcessAsync(client);
|
||||
|
||||
var response = await client.PatchAsJsonAsync(
|
||||
$"/api/nodes/{process.GetProperty("id").GetString()}/progress",
|
||||
new
|
||||
{
|
||||
completedQty = process.GetProperty("completedQty").GetInt32(),
|
||||
status = process.GetProperty("status").GetString(),
|
||||
riskLevel = process.GetProperty("riskLevel").GetString(),
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
private static int CountNodes(JsonElement node)
|
||||
{
|
||||
var count = 1;
|
||||
if (node.TryGetProperty("children", out var children))
|
||||
{
|
||||
count += children.EnumerateArray().Sum(CountNodes);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static int CountNodes(JsonElement node, string nodeType)
|
||||
{
|
||||
var count = node.GetProperty("nodeType").GetString() == nodeType ? 1 : 0;
|
||||
if (node.TryGetProperty("children", out var children))
|
||||
{
|
||||
count += children.EnumerateArray().Sum(child => CountNodes(child, nodeType));
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
private static async Task<JsonElement> GetFirstProcessAsync(HttpClient client)
|
||||
{
|
||||
var orders = await client.GetFromJsonAsync<JsonElement>("/api/orders?page=1&pageSize=20");
|
||||
var orderId = orders.GetProperty("items")[0].GetProperty("id").GetString();
|
||||
var root = await client.GetFromJsonAsync<JsonElement>($"/api/orders/{orderId}/tree");
|
||||
return FindFirstProcess(root);
|
||||
}
|
||||
|
||||
private static JsonElement FindFirstProcess(JsonElement node)
|
||||
{
|
||||
if (node.GetProperty("nodeType").GetString() == "process")
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
foreach (var child in node.GetProperty("children").EnumerateArray())
|
||||
{
|
||||
var match = FindFirstProcess(child);
|
||||
if (match.ValueKind != JsonValueKind.Undefined)
|
||||
{
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DashboardApiFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
private readonly string databaseName = $"dashboard-tests-{Guid.NewGuid():N}";
|
||||
|
||||
public RecordingDashboardUpdateQueue UpdateQueue { get; } = new();
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseEnvironment("Testing");
|
||||
builder.ConfigureAppConfiguration((_, configuration) =>
|
||||
{
|
||||
configuration.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Database:AutoMigrate"] = "true",
|
||||
["Database:Seed"] = "true",
|
||||
});
|
||||
});
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.RemoveAll<DbContextOptions<DashboardDbContext>>();
|
||||
services.AddDbContext<DashboardDbContext>(options =>
|
||||
options.UseInMemoryDatabase(databaseName));
|
||||
services.RemoveAll<IDashboardUpdateQueue>();
|
||||
services.AddSingleton<IDashboardUpdateQueue>(UpdateQueue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RecordingDashboardUpdateQueue : IDashboardUpdateQueue
|
||||
{
|
||||
public int EnqueueCount { get; private set; }
|
||||
|
||||
public ValueTask EnqueueAsync(
|
||||
UpdateNodeProgressResultDto update,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
EnqueueCount++;
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
EnqueueCount = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user