using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
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.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Transport;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Services;
using ZB.MOM.WW.ScadaBridge.Transport;
using ZB.MOM.WW.ScadaBridge.Transport.Import;
using ZB.MOM.WW.ScadaBridge.Transport.Serialization;
namespace ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests.Import;
///
/// Integration tests for Area-by-name transport (Task 7): a source instance
/// assigned to an under its site must carry that area's NAME
/// through the exported bundle so the importer's ResolveOrCreateAreaIdAsync
/// machinery can resolve-or-create the equivalent area under the target site. This
/// closes the half-shipped Area transport — the importer's GetOrCreate path was
/// already built + tested, but the exporter hardcoded AreaName: null.
///
/// Reuses the in-memory host pattern from SiteInstanceImportTests: real
/// repositories, real EF in-memory provider, real Transport pipeline.
///
///
public sealed class AreaTransportTests : IDisposable
{
private readonly ServiceProvider _provider;
public AreaTransportTests()
{
var services = new ServiceCollection();
services.AddSingleton(
new ConfigurationBuilder().AddInMemoryCollection().Build());
var dbName = $"AreaTransportTests_{Guid.NewGuid()}";
services.AddDbContext(opts => opts
.UseInMemoryDatabase(dbName)
.ConfigureWarnings(w => w.Ignore(InMemoryEventId.TransactionIgnoredWarning)));
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddTransport();
_provider = services.BuildServiceProvider();
}
public void Dispose() => _provider.Dispose();
[Fact]
public async Task Instance_Area_travels_by_name_and_resolves_under_target_site()
{
// ---- Seed a site + instance assigned to an Area "Line-1" ----
await using (var scope = _provider.CreateAsyncScope())
{
var ctx = scope.ServiceProvider.GetRequiredService();
var template = new Template("Pump") { Description = "pump tpl" };
ctx.Templates.Add(template);
var site = new Site("Plant 1", "plant-1");
ctx.Sites.Add(site);
await ctx.SaveChangesAsync();
var area = new Area("Line-1") { SiteId = site.Id };
ctx.Areas.Add(area);
await ctx.SaveChangesAsync();
var instance = new Instance("Pump-01")
{
TemplateId = template.Id,
SiteId = site.Id,
AreaId = area.Id,
State = InstanceState.Enabled,
};
ctx.Instances.Add(instance);
await ctx.SaveChangesAsync();
}
// ---- Export the whole site closure ----
Stream bundleStream;
await using (var scope = _provider.CreateAsyncScope())
{
var exporter = scope.ServiceProvider.GetRequiredService();
var ctx = scope.ServiceProvider.GetRequiredService();
var siteIds = await ctx.Sites.Select(s => s.Id).ToListAsync();
var selection = new ExportSelection(
TemplateIds: Array.Empty(),
SharedScriptIds: Array.Empty(),
ExternalSystemIds: Array.Empty(),
DatabaseConnectionIds: Array.Empty(),
NotificationListIds: Array.Empty(),
SmtpConfigurationIds: Array.Empty(),
ApiMethodIds: Array.Empty(),
IncludeDependencies: true,
SiteIds: siteIds);
bundleStream = await exporter.ExportAsync(selection, user: "alice", sourceEnvironment: "dev",
passphrase: null, cancellationToken: CancellationToken.None);
}
using var ms = new MemoryStream();
await bundleStream.CopyToAsync(ms);
ms.Position = 0;
// ---- Wipe the source rows so the import creates a fresh target ----
await using (var scope = _provider.CreateAsyncScope())
{
var ctx = scope.ServiceProvider.GetRequiredService();
ctx.Instances.RemoveRange(ctx.Instances);
ctx.Areas.RemoveRange(ctx.Areas);
ctx.Sites.RemoveRange(ctx.Sites);
ctx.Templates.RemoveRange(ctx.Templates);
await ctx.SaveChangesAsync();
// The template must exist for the instance to resolve its TemplateId.
ctx.Templates.Add(new Template("Pump") { Description = "pump tpl" });
await ctx.SaveChangesAsync();
}
// ---- Import with a CreateNew site mapping ----
Guid sessionId;
await using (var scope = _provider.CreateAsyncScope())
{
var importer = scope.ServiceProvider.GetRequiredService();
var session = await importer.LoadAsync(ms, passphrase: null);
sessionId = session.SessionId;
}
var nameMap = new BundleNameMap(
Sites: new[] { new SiteMapping("plant-1", MappingAction.CreateNew, null) },
Connections: Array.Empty());
await using (var scope = _provider.CreateAsyncScope())
{
var importer = scope.ServiceProvider.GetRequiredService();
await importer.ApplyAsync(
sessionId,
new List
{
new("Template", "Pump", ResolutionAction.Skip, null),
new("Site", "plant-1", ResolutionAction.Add, null),
new("Instance", "Pump-01", ResolutionAction.Add, null),
},
user: "bob",
ct: CancellationToken.None,
nameMap: nameMap);
}
// ---- Assert: the imported instance's AreaId resolves to an Area named
// "Line-1" under the target site (NOT null). ----
await using (var scope = _provider.CreateAsyncScope())
{
var ctx = scope.ServiceProvider.GetRequiredService();
var site = await ctx.Sites.SingleAsync(s => s.SiteIdentifier == "plant-1");
var inst = await ctx.Instances.SingleAsync(i => i.UniqueName == "Pump-01");
Assert.NotNull(inst.AreaId);
var area = await ctx.Areas.SingleAsync(a => a.Id == inst.AreaId);
Assert.Equal("Line-1", area.Name);
Assert.Equal(site.Id, area.SiteId);
}
}
}