513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
250 lines
9.7 KiB
C#
250 lines
9.7 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;
|
|
|
|
/// <summary>Initializes a new instance of the <see cref="ListValueNormalizerTests"/> class.</summary>
|
|
public ListValueNormalizerTests()
|
|
{
|
|
_context = SqliteTestHelper.CreateInMemoryContext();
|
|
}
|
|
|
|
/// <summary>Releases the resources held by this test class.</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>Verifies the case: template Attribute, old String Form, rewritten To Native.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: template Attribute, already Native, is Not Rewritten.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: template Attribute, string List, is Unchanged.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: template Attribute, malformed, is Skipped, and Sibling Still Normalized.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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
|
|
}
|
|
|
|
/// <summary>Verifies the case: template Attribute, non List Row, is Unchanged.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: instance Attribute Override, old String Form, rewritten To Native.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: instance Attribute Override, null Element Type, is Untouched.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
|
|
/// <summary>Verifies the case: normalize Async, is Idempotent, second Run Changes Nothing.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[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);
|
|
}
|
|
}
|