191 lines
6.8 KiB
C#
191 lines
6.8 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
|
|
|
/// <summary>
|
|
/// Verifies <see cref="UnsTreeService.DeleteClusterAsync"/> and
|
|
/// <see cref="UnsTreeService.DeleteEnterpriseAsync"/>: refuse-if-children deletes that
|
|
/// mirror the Area/Line/Equipment convention but carry no <c>RowVersion</c> concurrency
|
|
/// token (the <see cref="ServerCluster"/> entity has none — Option B, no migration).
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The EF InMemory provider does not enforce FK <c>Restrict</c>, so the refuse-if-children
|
|
/// behaviour is driven by an explicit child pre-check in the service (not the DB throwing),
|
|
/// which is what these tests exercise.
|
|
/// </remarks>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class UnsTreeServiceDeleteClusterTests
|
|
{
|
|
private static (UnsTreeService Service, string DbName) Fresh()
|
|
{
|
|
var dbName = $"uns-delcluster-{Guid.NewGuid():N}";
|
|
return (new UnsTreeService(UnsTreeTestDb.Factory(dbName)), dbName);
|
|
}
|
|
|
|
private static ServerCluster NewCluster(string clusterId, string enterprise) => new()
|
|
{
|
|
ClusterId = clusterId,
|
|
Name = clusterId,
|
|
Enterprise = enterprise,
|
|
Site = "site",
|
|
RedundancyMode = RedundancyMode.None,
|
|
CreatedBy = "test",
|
|
};
|
|
|
|
// ----- DeleteCluster -----
|
|
|
|
/// <summary>Deleting a child-free cluster removes the row.</summary>
|
|
[Fact]
|
|
public async Task DeleteCluster_empty_removes_row()
|
|
{
|
|
var (service, dbName) = Fresh();
|
|
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
|
{
|
|
db.ServerClusters.Add(NewCluster("CL-EMPTY", "zb"));
|
|
db.SaveChanges();
|
|
}
|
|
|
|
var result = await service.DeleteClusterAsync("CL-EMPTY");
|
|
|
|
result.Ok.ShouldBeTrue();
|
|
result.Error.ShouldBeNull();
|
|
|
|
using var verify = UnsTreeTestDb.CreateNamed(dbName);
|
|
verify.ServerClusters.Any(c => c.ClusterId == "CL-EMPTY").ShouldBeFalse();
|
|
}
|
|
|
|
/// <summary>Deleting a cluster that is already gone is a no-op success (idempotent).</summary>
|
|
[Fact]
|
|
public async Task DeleteCluster_already_gone_returns_ok()
|
|
{
|
|
var (service, _) = Fresh();
|
|
|
|
var result = await service.DeleteClusterAsync("GHOST-CLUSTER");
|
|
|
|
result.Ok.ShouldBeTrue();
|
|
result.Error.ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>A cluster with a UNS area refuses deletion with a friendly message and stays put.</summary>
|
|
[Fact]
|
|
public async Task DeleteCluster_with_area_refuses_and_keeps_row()
|
|
{
|
|
var (service, dbName) = Fresh();
|
|
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
|
{
|
|
db.ServerClusters.Add(NewCluster("CL-AREA", "zb"));
|
|
db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-CL", ClusterId = "CL-AREA", Name = "a" });
|
|
db.SaveChanges();
|
|
}
|
|
|
|
var result = await service.DeleteClusterAsync("CL-AREA");
|
|
|
|
result.Ok.ShouldBeFalse();
|
|
result.Error.ShouldNotBeNull();
|
|
result.Error.ShouldContain("CL-AREA");
|
|
|
|
using var verify = UnsTreeTestDb.CreateNamed(dbName);
|
|
verify.ServerClusters.Any(c => c.ClusterId == "CL-AREA").ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>A cluster with a driver instance refuses deletion and stays put.</summary>
|
|
[Fact]
|
|
public async Task DeleteCluster_with_driver_refuses_and_keeps_row()
|
|
{
|
|
var (service, dbName) = Fresh();
|
|
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
|
{
|
|
db.ServerClusters.Add(NewCluster("CL-DRV", "zb"));
|
|
db.DriverInstances.Add(new DriverInstance
|
|
{
|
|
DriverInstanceId = "DRV-CL",
|
|
ClusterId = "CL-DRV",
|
|
NamespaceId = "NS-1",
|
|
Name = "drv",
|
|
DriverType = "ModbusTcp",
|
|
DriverConfig = "{}",
|
|
});
|
|
db.SaveChanges();
|
|
}
|
|
|
|
var result = await service.DeleteClusterAsync("CL-DRV");
|
|
|
|
result.Ok.ShouldBeFalse();
|
|
result.Error.ShouldNotBeNull();
|
|
|
|
using var verify = UnsTreeTestDb.CreateNamed(dbName);
|
|
verify.ServerClusters.Any(c => c.ClusterId == "CL-DRV").ShouldBeTrue();
|
|
}
|
|
|
|
// ----- DeleteEnterprise -----
|
|
|
|
/// <summary>Deleting an enterprise whose every cluster is child-free removes them all.</summary>
|
|
[Fact]
|
|
public async Task DeleteEnterprise_all_empty_removes_all_clusters()
|
|
{
|
|
var (service, dbName) = Fresh();
|
|
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
|
{
|
|
db.ServerClusters.Add(NewCluster("ENT1-A", "ent-1"));
|
|
db.ServerClusters.Add(NewCluster("ENT1-B", "ent-1"));
|
|
// A cluster under a different enterprise must survive.
|
|
db.ServerClusters.Add(NewCluster("ENT2-A", "ent-2"));
|
|
db.SaveChanges();
|
|
}
|
|
|
|
var result = await service.DeleteEnterpriseAsync("ent-1");
|
|
|
|
result.Ok.ShouldBeTrue();
|
|
result.Error.ShouldBeNull();
|
|
|
|
using var verify = UnsTreeTestDb.CreateNamed(dbName);
|
|
verify.ServerClusters.Any(c => c.Enterprise == "ent-1").ShouldBeFalse();
|
|
verify.ServerClusters.Any(c => c.ClusterId == "ENT2-A").ShouldBeTrue();
|
|
}
|
|
|
|
/// <summary>Deleting an enterprise with no matching clusters is a no-op success.</summary>
|
|
[Fact]
|
|
public async Task DeleteEnterprise_no_matches_returns_ok()
|
|
{
|
|
var (service, _) = Fresh();
|
|
|
|
var result = await service.DeleteEnterpriseAsync("nonexistent-enterprise");
|
|
|
|
result.Ok.ShouldBeTrue();
|
|
result.Error.ShouldBeNull();
|
|
}
|
|
|
|
/// <summary>
|
|
/// When one cluster under the enterprise has children, the whole delete refuses (naming the
|
|
/// blocking cluster) and nothing is deleted — no partial enterprise removal.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task DeleteEnterprise_one_cluster_with_children_refuses_and_deletes_nothing()
|
|
{
|
|
var (service, dbName) = Fresh();
|
|
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
|
{
|
|
db.ServerClusters.Add(NewCluster("ENT3-EMPTY", "ent-3"));
|
|
db.ServerClusters.Add(NewCluster("ENT3-FULL", "ent-3"));
|
|
db.UnsAreas.Add(new UnsArea { UnsAreaId = "AREA-ENT3", ClusterId = "ENT3-FULL", Name = "a" });
|
|
db.SaveChanges();
|
|
}
|
|
|
|
var result = await service.DeleteEnterpriseAsync("ent-3");
|
|
|
|
result.Ok.ShouldBeFalse();
|
|
result.Error.ShouldNotBeNull();
|
|
result.Error.ShouldContain("ENT3-FULL");
|
|
|
|
// Nothing partial: BOTH clusters must still be present.
|
|
using var verify = UnsTreeTestDb.CreateNamed(dbName);
|
|
verify.ServerClusters.Any(c => c.ClusterId == "ENT3-EMPTY").ShouldBeTrue();
|
|
verify.ServerClusters.Any(c => c.ClusterId == "ENT3-FULL").ShouldBeTrue();
|
|
}
|
|
}
|