using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using ScadaLink.Commons.Entities.Audit; using ScadaLink.Commons.Entities.Deployment; using ScadaLink.Commons.Entities.Instances; using ScadaLink.Commons.Entities.Security; using ScadaLink.Commons.Entities.Sites; using ScadaLink.Commons.Entities.Templates; using ScadaLink.Commons.Types.Enums; using ScadaLink.ConfigurationDatabase; using ScadaLink.ConfigurationDatabase.Repositories; namespace ScadaLink.ConfigurationDatabase.Tests; public class SecurityRepositoryTests : IDisposable { private readonly ScadaLinkDbContext _context; private readonly SecurityRepository _repository; public SecurityRepositoryTests() { var options = new DbContextOptionsBuilder() .UseSqlite("DataSource=:memory:") .Options; _context = new ScadaLinkDbContext(options); _context.Database.OpenConnection(); _context.Database.EnsureCreated(); _repository = new SecurityRepository(_context); } public void Dispose() { _context.Database.CloseConnection(); _context.Dispose(); } [Fact] public async Task AddMapping_AndGetById_ReturnsMapping() { var mapping = new LdapGroupMapping("CN=Admins,DC=test", "Admin"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); var loaded = await _repository.GetMappingByIdAsync(mapping.Id); Assert.NotNull(loaded); Assert.Equal("CN=Admins,DC=test", loaded.LdapGroupName); Assert.Equal("Admin", loaded.Role); } [Fact] public async Task GetAllMappings_ReturnsAll() { await _repository.AddMappingAsync(new LdapGroupMapping("Group1", "Admin")); await _repository.AddMappingAsync(new LdapGroupMapping("Group2", "Design")); await _repository.SaveChangesAsync(); // +1 for seed data var all = await _repository.GetAllMappingsAsync(); Assert.True(all.Count >= 2); } [Fact] public async Task GetMappingsByRole_FiltersCorrectly() { await _repository.AddMappingAsync(new LdapGroupMapping("Designers", "Design")); await _repository.AddMappingAsync(new LdapGroupMapping("Deployers", "Deployment")); await _repository.SaveChangesAsync(); var designMappings = await _repository.GetMappingsByRoleAsync("Design"); // Seed data includes "SCADA-Designers" with role "Design", plus the one we added Assert.Equal(2, designMappings.Count); Assert.Contains(designMappings, m => m.LdapGroupName == "Designers"); } [Fact] public async Task UpdateMapping_PersistsChange() { var mapping = new LdapGroupMapping("OldGroup", "Admin"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); mapping.Role = "Design"; await _repository.UpdateMappingAsync(mapping); await _repository.SaveChangesAsync(); _context.ChangeTracker.Clear(); var loaded = await _repository.GetMappingByIdAsync(mapping.Id); Assert.Equal("Design", loaded!.Role); } [Fact] public async Task DeleteMapping_RemovesEntity() { var mapping = new LdapGroupMapping("ToDelete", "Admin"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); await _repository.DeleteMappingAsync(mapping.Id); await _repository.SaveChangesAsync(); var loaded = await _repository.GetMappingByIdAsync(mapping.Id); Assert.Null(loaded); } [Fact] public async Task AddScopeRule_AndGetForMapping() { var site = new Site("Site1", "SITE-001"); _context.Sites.Add(site); var mapping = new LdapGroupMapping("Deployers", "Deployment"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); var rule = new SiteScopeRule { LdapGroupMappingId = mapping.Id, SiteId = site.Id }; await _repository.AddScopeRuleAsync(rule); await _repository.SaveChangesAsync(); var rules = await _repository.GetScopeRulesForMappingAsync(mapping.Id); Assert.Single(rules); Assert.Equal(site.Id, rules[0].SiteId); } [Fact] public async Task GetScopeRuleById_ReturnsRule() { var site = new Site("Site1", "SITE-001"); _context.Sites.Add(site); var mapping = new LdapGroupMapping("Group", "Deployment"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); var rule = new SiteScopeRule { LdapGroupMappingId = mapping.Id, SiteId = site.Id }; await _repository.AddScopeRuleAsync(rule); await _repository.SaveChangesAsync(); var loaded = await _repository.GetScopeRuleByIdAsync(rule.Id); Assert.NotNull(loaded); Assert.Equal(mapping.Id, loaded.LdapGroupMappingId); } [Fact] public async Task UpdateScopeRule_PersistsChange() { var site1 = new Site("Site1", "SITE-001"); var site2 = new Site("Site2", "SITE-002"); _context.Sites.AddRange(site1, site2); var mapping = new LdapGroupMapping("Group", "Deployment"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); var rule = new SiteScopeRule { LdapGroupMappingId = mapping.Id, SiteId = site1.Id }; await _repository.AddScopeRuleAsync(rule); await _repository.SaveChangesAsync(); rule.SiteId = site2.Id; await _repository.UpdateScopeRuleAsync(rule); await _repository.SaveChangesAsync(); _context.ChangeTracker.Clear(); var loaded = await _repository.GetScopeRuleByIdAsync(rule.Id); Assert.Equal(site2.Id, loaded!.SiteId); } [Fact] public async Task DeleteScopeRule_RemovesEntity() { var site = new Site("Site1", "SITE-001"); _context.Sites.Add(site); var mapping = new LdapGroupMapping("Group", "Deployment"); await _repository.AddMappingAsync(mapping); await _repository.SaveChangesAsync(); var rule = new SiteScopeRule { LdapGroupMappingId = mapping.Id, SiteId = site.Id }; await _repository.AddScopeRuleAsync(rule); await _repository.SaveChangesAsync(); await _repository.DeleteScopeRuleAsync(rule.Id); await _repository.SaveChangesAsync(); var loaded = await _repository.GetScopeRuleByIdAsync(rule.Id); Assert.Null(loaded); } } public class CentralUiRepositoryTests : IDisposable { private readonly ScadaLinkDbContext _context; private readonly CentralUiRepository _repository; public CentralUiRepositoryTests() { _context = SqliteTestHelper.CreateInMemoryContext(); _repository = new CentralUiRepository(_context); } public void Dispose() { _context.Database.CloseConnection(); _context.Dispose(); } [Fact] public async Task GetAllSites_ReturnsOrderedByName() { _context.Sites.AddRange( new Site("Zulu", "Z-001"), new Site("Alpha", "A-001")); await _context.SaveChangesAsync(); var sites = await _repository.GetAllSitesAsync(); Assert.Equal(2, sites.Count); Assert.Equal("Alpha", sites[0].Name); Assert.Equal("Zulu", sites[1].Name); } [Fact] public async Task GetInstancesFiltered_BySiteId() { var site1 = new Site("Site1", "S-001"); var site2 = new Site("Site2", "S-002"); var template = new Template("T1"); _context.Sites.AddRange(site1, site2); _context.Templates.Add(template); await _context.SaveChangesAsync(); _context.Instances.AddRange( new Instance("Inst1") { SiteId = site1.Id, TemplateId = template.Id }, new Instance("Inst2") { SiteId = site2.Id, TemplateId = template.Id }); await _context.SaveChangesAsync(); var instances = await _repository.GetInstancesFilteredAsync(siteId: site1.Id); Assert.Single(instances); Assert.Equal("Inst1", instances[0].UniqueName); } [Fact] public async Task GetInstancesFiltered_BySearchTerm() { var site = new Site("Site1", "S-001"); var template = new Template("T1"); _context.Sites.Add(site); _context.Templates.Add(template); await _context.SaveChangesAsync(); _context.Instances.AddRange( new Instance("PumpStation1") { SiteId = site.Id, TemplateId = template.Id }, new Instance("TankLevel1") { SiteId = site.Id, TemplateId = template.Id }); await _context.SaveChangesAsync(); var instances = await _repository.GetInstancesFilteredAsync(searchTerm: "Pump"); Assert.Single(instances); } [Fact] public async Task GetRecentDeployments_ReturnsInReverseChronological() { var site = new Site("Site1", "S-001"); var template = new Template("T1"); _context.Sites.Add(site); _context.Templates.Add(template); await _context.SaveChangesAsync(); var instance = new Instance("I1") { SiteId = site.Id, TemplateId = template.Id }; _context.Instances.Add(instance); await _context.SaveChangesAsync(); _context.DeploymentRecords.AddRange( new DeploymentRecord("d-001", "admin") { InstanceId = instance.Id, DeployedAt = DateTimeOffset.UtcNow.AddHours(-2) }, new DeploymentRecord("d-002", "admin") { InstanceId = instance.Id, DeployedAt = DateTimeOffset.UtcNow.AddHours(-1) }, new DeploymentRecord("d-003", "admin") { InstanceId = instance.Id, DeployedAt = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var recent = await _repository.GetRecentDeploymentsAsync(2); Assert.Equal(2, recent.Count); Assert.Equal("d-003", recent[0].DeploymentId); Assert.Equal("d-002", recent[1].DeploymentId); } [Fact] public async Task GetAuditLogEntries_FiltersByUser() { _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "T1") { Timestamp = DateTimeOffset.UtcNow }, new AuditLogEntry("user1", "Update", "Instance", "2", "I1") { Timestamp = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(user: "admin"); Assert.Single(entries); Assert.Equal(1, total); Assert.Equal("admin", entries[0].User); } [Fact] public async Task GetAuditLogEntries_FiltersByEntityType() { _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "T1") { Timestamp = DateTimeOffset.UtcNow }, new AuditLogEntry("admin", "Create", "Instance", "2", "I1") { Timestamp = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(entityType: "Template"); Assert.Single(entries); Assert.Equal(1, total); } [Fact] public async Task GetAuditLogEntries_FiltersByActionType() { _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "T1") { Timestamp = DateTimeOffset.UtcNow }, new AuditLogEntry("admin", "Delete", "Template", "2", "T2") { Timestamp = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(action: "Delete"); Assert.Single(entries); } [Fact] public async Task GetAuditLogEntries_FiltersByTimeRange() { var now = DateTimeOffset.UtcNow; _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "T1") { Timestamp = now.AddHours(-5) }, new AuditLogEntry("admin", "Update", "Template", "2", "T2") { Timestamp = now.AddHours(-1) }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(from: now.AddHours(-2)); Assert.Single(entries); } [Fact] public async Task GetAuditLogEntries_FiltersByEntityId() { _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "T1") { Timestamp = DateTimeOffset.UtcNow }, new AuditLogEntry("admin", "Create", "Template", "2", "T2") { Timestamp = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(entityId: "1"); Assert.Single(entries); } [Fact] public async Task GetAuditLogEntries_FiltersByEntityName() { _context.AuditLogEntries.AddRange( new AuditLogEntry("admin", "Create", "Template", "1", "PumpStation") { Timestamp = DateTimeOffset.UtcNow }, new AuditLogEntry("admin", "Create", "Template", "2", "TankLevel") { Timestamp = DateTimeOffset.UtcNow }); await _context.SaveChangesAsync(); var (entries, total) = await _repository.GetAuditLogEntriesAsync(entityName: "Pump"); Assert.Single(entries); } [Fact] public async Task GetAuditLogEntries_ReverseChronologicalWithPagination() { var now = DateTimeOffset.UtcNow; for (int i = 0; i < 10; i++) { _context.AuditLogEntries.Add(new AuditLogEntry("admin", "Create", "Template", i.ToString(), $"T{i}") { Timestamp = now.AddMinutes(i) }); } await _context.SaveChangesAsync(); var (page1, total) = await _repository.GetAuditLogEntriesAsync(page: 1, pageSize: 3); Assert.Equal(10, total); Assert.Equal(3, page1.Count); Assert.Equal("T9", page1[0].EntityName); // Most recent first var (page2, _) = await _repository.GetAuditLogEntriesAsync(page: 2, pageSize: 3); Assert.Equal(3, page2.Count); Assert.Equal("T6", page2[0].EntityName); } [Fact] public async Task GetTemplateTree_IncludesChildren() { var template = new Template("TestTemplate"); template.Attributes.Add(new TemplateAttribute("Attr1") { DataType = DataType.Int32 }); _context.Templates.Add(template); await _context.SaveChangesAsync(); var tree = await _repository.GetTemplateTreeAsync(); Assert.NotEmpty(tree); var loaded = tree.First(t => t.Name == "TestTemplate"); Assert.Single(loaded.Attributes); } [Fact] public async Task GetAreaTree_ReturnsHierarchy() { var site = new Site("Site1", "S-001"); _context.Sites.Add(site); await _context.SaveChangesAsync(); var parent = new Area("Building A") { SiteId = site.Id }; _context.Areas.Add(parent); await _context.SaveChangesAsync(); var child = new Area("Floor 1") { SiteId = site.Id, ParentAreaId = parent.Id }; _context.Areas.Add(child); await _context.SaveChangesAsync(); var areas = await _repository.GetAreaTreeBySiteIdAsync(site.Id); Assert.Equal(2, areas.Count); } }