refactor: simplify data connections from many-to-many site assignment to direct site ownership

Replace SiteDataConnectionAssignment join table with a direct SiteId FK on DataConnection,
simplifying the data model, repositories, UI, CLI, and deployment service.
This commit is contained in:
Joseph Doherty
2026-03-21 21:07:10 -04:00
parent cd6efeea90
commit 970d0a5cb3
25 changed files with 1543 additions and 490 deletions

View File

@@ -45,7 +45,6 @@ public class DbContextTests : IDisposable
Assert.NotNull(_context.Areas);
Assert.NotNull(_context.Sites);
Assert.NotNull(_context.DataConnections);
Assert.NotNull(_context.SiteDataConnectionAssignments);
Assert.NotNull(_context.DeploymentRecords);
Assert.NotNull(_context.SystemArtifactDeploymentRecords);
Assert.NotNull(_context.ExternalSystemDefinitions);
@@ -133,9 +132,11 @@ public class DbContextTests : IDisposable
{
var site = new Site("Site1", "SITE-001");
var template = new Template("Template1");
var dataConn = new DataConnection("OpcConn", "OpcUa");
_context.Sites.Add(site);
_context.Templates.Add(template);
_context.SaveChanges();
var dataConn = new DataConnection("OpcConn", "OpcUa", site.Id);
_context.DataConnections.Add(dataConn);
_context.SaveChanges();
@@ -300,19 +301,18 @@ public class DbContextTests : IDisposable
}
[Fact]
public void SiteDataConnectionAssignment_CreatesBothForeignKeys()
public void DataConnection_BelongsToSite()
{
var site = new Site("Site1", "SITE-001");
var conn = new DataConnection("OpcConn", "OpcUa");
_context.Sites.Add(site);
_context.SaveChanges();
var conn = new DataConnection("OpcConn", "OpcUa", site.Id);
_context.DataConnections.Add(conn);
_context.SaveChanges();
var assignment = new SiteDataConnectionAssignment { SiteId = site.Id, DataConnectionId = conn.Id };
_context.SiteDataConnectionAssignments.Add(assignment);
_context.SaveChanges();
Assert.Single(_context.SiteDataConnectionAssignments.ToList());
var loaded = _context.DataConnections.Single(c => c.Name == "OpcConn");
Assert.Equal(site.Id, loaded.SiteId);
}
[Fact]

View File

@@ -37,9 +37,8 @@ public class ArtifactDeploymentServiceTests
_siteRepo.GetAllSitesAsync().Returns(new List<Site>());
var service = CreateService();
var command = CreateCommand();
var result = await service.DeployToAllSitesAsync(command, "admin");
var result = await service.DeployToAllSitesAsync("admin");
Assert.True(result.IsFailure);
Assert.Contains("No sites", result.Error);

View File

@@ -200,7 +200,7 @@ public class FlatteningServiceTests
var connections = new Dictionary<int, DataConnection>
{
[100] = new("OPC-Server1", "OpcUa") { Id = 100, Configuration = "opc.tcp://localhost:4840" }
[100] = new("OPC-Server1", "OpcUa", 1) { Id = 100, Configuration = "opc.tcp://localhost:4840" }
};
var result = _sut.Flatten(

View File

@@ -93,44 +93,12 @@ public class SiteServiceTests
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
var result = await _sut.CreateDataConnectionAsync("OPC-Server1", "OpcUa", "{\"url\":\"opc.tcp://localhost\"}", "admin");
var result = await _sut.CreateDataConnectionAsync(1, "OPC-Server1", "OpcUa", "{\"url\":\"opc.tcp://localhost\"}", "admin");
Assert.True(result.IsSuccess);
Assert.Equal("OPC-Server1", result.Value.Name);
Assert.Equal("OpcUa", result.Value.Protocol);
}
[Fact]
public async Task AssignConnectionToSite_AlreadyAssigned_ReturnsFailure()
{
_repoMock.Setup(r => r.GetSiteByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Site("S", "S1") { Id = 1 });
_repoMock.Setup(r => r.GetDataConnectionByIdAsync(100, It.IsAny<CancellationToken>()))
.ReturnsAsync(new DataConnection("Conn", "OpcUa") { Id = 100 });
_repoMock.Setup(r => r.GetSiteDataConnectionAssignmentAsync(1, 100, It.IsAny<CancellationToken>()))
.ReturnsAsync(new SiteDataConnectionAssignment { Id = 1, SiteId = 1, DataConnectionId = 100 });
var result = await _sut.AssignConnectionToSiteAsync(1, 100, "admin");
Assert.True(result.IsFailure);
Assert.Contains("already assigned", result.Error);
}
[Fact]
public async Task AssignConnectionToSite_Valid_Success()
{
_repoMock.Setup(r => r.GetSiteByIdAsync(1, It.IsAny<CancellationToken>()))
.ReturnsAsync(new Site("S", "S1") { Id = 1 });
_repoMock.Setup(r => r.GetDataConnectionByIdAsync(100, It.IsAny<CancellationToken>()))
.ReturnsAsync(new DataConnection("Conn", "OpcUa") { Id = 100 });
_repoMock.Setup(r => r.GetSiteDataConnectionAssignmentAsync(1, 100, It.IsAny<CancellationToken>()))
.ReturnsAsync((SiteDataConnectionAssignment?)null);
_repoMock.Setup(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(1);
var result = await _sut.AssignConnectionToSiteAsync(1, 100, "admin");
Assert.True(result.IsSuccess);
Assert.Equal(1, result.Value.SiteId);
}
[Fact]