Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/ListValueNormalizerTests.cs
T

232 lines
8.0 KiB
C#

using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests;
/// <summary>
/// Tests for <see cref="ListValueNormalizer"/> — the idempotent startup normalizer that
/// rewrites already-persisted List attribute values from the old array-of-strings JSON
/// form (<c>["10","20"]</c>) to the new native-typed form (<c>[10,20]</c>).
/// </summary>
public class ListValueNormalizerTests : IDisposable
{
private readonly ScadaBridgeDbContext _context;
public ListValueNormalizerTests()
{
_context = SqliteTestHelper.CreateInMemoryContext();
}
public void Dispose()
{
_context.Database.CloseConnection();
_context.Dispose();
}
// Seeds a Template parent (satisfies the TemplateAttribute -> Template FK) and returns its Id.
private async Task<int> SeedTemplateAsync()
{
var template = new Template("T1");
_context.Templates.Add(template);
await _context.SaveChangesAsync();
return template.Id;
}
// Seeds Site + Template + Instance (satisfies the InstanceAttributeOverride -> Instance FK)
// and returns the Instance Id.
private async Task<int> SeedInstanceAsync()
{
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("Inst1") { SiteId = site.Id, TemplateId = template.Id };
_context.Instances.Add(instance);
await _context.SaveChangesAsync();
return instance.Id;
}
[Fact]
public async Task TemplateAttribute_OldStringForm_RewrittenToNative()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("intList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.Int32,
Value = "[\"10\",\"20\"]",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("[10,20]", reloaded.Value);
}
[Fact]
public async Task TemplateAttribute_AlreadyNative_IsNotRewritten()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("intList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.Int32,
Value = "[10,20]",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
// No tracked entity should be marked Modified — idempotent no-op.
var tracked = _context.ChangeTracker.Entries<TemplateAttribute>()
.Where(e => e.State == EntityState.Modified)
.ToList();
Assert.Empty(tracked);
var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("[10,20]", reloaded.Value);
}
[Fact]
public async Task TemplateAttribute_StringList_IsUnchanged()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("stringList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.String,
Value = "[\"a\",\"b\"]",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("[\"a\",\"b\"]", reloaded.Value);
}
[Fact]
public async Task TemplateAttribute_Malformed_IsSkipped_AndSiblingStillNormalized()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("badList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.Int32,
Value = "[\"a\"", // malformed JSON
});
_context.TemplateAttributes.Add(new TemplateAttribute("goodList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.Int32,
Value = "[\"5\"]", // old form, valid
});
await _context.SaveChangesAsync();
// Must NOT throw despite the malformed row.
await ListValueNormalizer.NormalizeAsync(_context);
var bad = await _context.TemplateAttributes.AsNoTracking()
.SingleAsync(a => a.Name == "badList");
var good = await _context.TemplateAttributes.AsNoTracking()
.SingleAsync(a => a.Name == "goodList");
Assert.Equal("[\"a\"", bad.Value); // skipped, untouched
Assert.Equal("[5]", good.Value); // normalized
}
[Fact]
public async Task TemplateAttribute_NonListRow_IsUnchanged()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("scalar")
{
TemplateId = templateId,
DataType = DataType.Int32,
ElementDataType = null,
Value = "42",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var reloaded = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("42", reloaded.Value);
}
[Fact]
public async Task InstanceAttributeOverride_OldStringForm_RewrittenToNative()
{
var instanceId = await SeedInstanceAsync();
_context.InstanceAttributeOverrides.Add(new InstanceAttributeOverride("intList")
{
InstanceId = instanceId,
ElementDataType = DataType.Int32,
OverrideValue = "[\"5\"]",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var reloaded = await _context.InstanceAttributeOverrides.AsNoTracking().SingleAsync();
Assert.Equal("[5]", reloaded.OverrideValue);
}
[Fact]
public async Task InstanceAttributeOverride_NullElementType_IsUntouched()
{
var instanceId = await SeedInstanceAsync();
_context.InstanceAttributeOverrides.Add(new InstanceAttributeOverride("scalar")
{
InstanceId = instanceId,
ElementDataType = null,
OverrideValue = "42",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var reloaded = await _context.InstanceAttributeOverrides.AsNoTracking().SingleAsync();
Assert.Equal("42", reloaded.OverrideValue);
}
[Fact]
public async Task NormalizeAsync_IsIdempotent_SecondRunChangesNothing()
{
var templateId = await SeedTemplateAsync();
_context.TemplateAttributes.Add(new TemplateAttribute("intList")
{
TemplateId = templateId,
DataType = DataType.List,
ElementDataType = DataType.Int32,
Value = "[\"10\",\"20\"]",
});
await _context.SaveChangesAsync();
await ListValueNormalizer.NormalizeAsync(_context);
var afterFirst = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("[10,20]", afterFirst.Value);
await ListValueNormalizer.NormalizeAsync(_context);
var afterSecond = await _context.TemplateAttributes.AsNoTracking().SingleAsync();
Assert.Equal("[10,20]", afterSecond.Value);
var tracked = _context.ChangeTracker.Entries<TemplateAttribute>()
.Where(e => e.State == EntityState.Modified)
.ToList();
Assert.Empty(tracked);
}
}