refactor(configmanager): migrate to per-file pipeline system
Align ConfigManager with DataSync's per-file pipeline format (pipeline.*.json) by reusing EtlPipelineConfig types directly, eliminating duplicate models and simplifying the codebase. Removes ~3200 lines of obsolete code.
This commit is contained in:
-287
@@ -1,287 +0,0 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class PipelineFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel
|
||||
{
|
||||
Source = new PipelineSource
|
||||
{
|
||||
Connection = "jde",
|
||||
Query = "SELECT * FROM Test"
|
||||
},
|
||||
Destination = new PipelineDestination
|
||||
{
|
||||
Table = "TestTable",
|
||||
MatchColumns = ["Id", "Name"]
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineFormViewModel("TestPipeline", model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Name.ShouldBe("TestPipeline");
|
||||
sut.Connection.ShouldBe("jde");
|
||||
sut.Query.ShouldBe("SELECT * FROM Test");
|
||||
sut.DestinationTable.ShouldBe("TestTable");
|
||||
sut.MatchColumnsText.ShouldBe("Id\nName");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineFormViewModel("Test", model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.Connection = "cms";
|
||||
|
||||
// Assert
|
||||
model.Source.Connection.ShouldBe("cms");
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchColumnsText_SplitsIntoArray()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel();
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
|
||||
// Act
|
||||
sut.MatchColumnsText = "Col1\nCol2\nCol3";
|
||||
|
||||
// Assert
|
||||
model.Destination.MatchColumns.Length.ShouldBe(3);
|
||||
model.Destination.MatchColumns[0].ShouldBe("Col1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Schedules_AreInitialized()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel
|
||||
{
|
||||
Schedules = new PipelineSchedules
|
||||
{
|
||||
Mass = new ScheduleModel { Enabled = true, IntervalMinutes = 10080 }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.MassSchedule.ShouldNotBeNull();
|
||||
sut.MassSchedule.Enabled.ShouldBeTrue();
|
||||
sut.MassSchedule.IntervalMinutes.ShouldBe(10080);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullSchedules_AreInitializedToDefault()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel
|
||||
{
|
||||
Schedules = new PipelineSchedules
|
||||
{
|
||||
Mass = null,
|
||||
Daily = null,
|
||||
Hourly = null
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.MassSchedule.ShouldNotBeNull();
|
||||
sut.DailySchedule.ShouldNotBeNull();
|
||||
sut.HourlySchedule.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFromUpdateText_JoinsAndSplits()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel
|
||||
{
|
||||
Destination = new PipelineDestination
|
||||
{
|
||||
ExcludeFromUpdate = ["CreatedDate", "ModifiedDate"]
|
||||
}
|
||||
};
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
|
||||
// Assert - verify getter joins correctly
|
||||
sut.ExcludeFromUpdateText.ShouldBe("CreatedDate\nModifiedDate");
|
||||
|
||||
// Act - verify setter splits correctly
|
||||
sut.ExcludeFromUpdateText = "Col1\nCol2";
|
||||
model.Destination.ExcludeFromUpdate.Length.ShouldBe(2);
|
||||
model.Destination.ExcludeFromUpdate[0].ShouldBe("Col1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PostScriptsText_HandlesNullable()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel { PostScripts = null };
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
|
||||
// Assert - null should return empty string
|
||||
sut.PostScriptsText.ShouldBe(string.Empty);
|
||||
|
||||
// Act - set some scripts
|
||||
sut.PostScriptsText = "script1.sql\nscript2.sql";
|
||||
model.PostScripts.ShouldNotBeNull();
|
||||
model.PostScripts!.Length.ShouldBe(2);
|
||||
|
||||
// Act - clear scripts by setting empty
|
||||
sut.PostScriptsText = "";
|
||||
model.PostScripts.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MassQuery_Property_ReadsAndWrites()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel
|
||||
{
|
||||
Source = new PipelineSource
|
||||
{
|
||||
MassQuery = "SELECT * FROM Test WHERE All = 1"
|
||||
}
|
||||
};
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineFormViewModel("Test", model, () => changedInvoked = true);
|
||||
|
||||
// Assert - verify getter
|
||||
sut.MassQuery.ShouldBe("SELECT * FROM Test WHERE All = 1");
|
||||
|
||||
// Act - verify setter
|
||||
sut.MassQuery = "SELECT * FROM NewTable";
|
||||
model.Source.MassQuery.ShouldBe("SELECT * FROM NewTable");
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel();
|
||||
var sut = new PipelineFormViewModel("Test", model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(PipelineFormViewModel.Query))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Query = "SELECT * FROM NewQuery";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScheduleChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new PipelineModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new PipelineFormViewModel("Test", model, () => changedInvoked = true);
|
||||
|
||||
// Act - change schedule property (Enabled defaults to true, so set to false)
|
||||
sut.MassSchedule.Enabled = false;
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
public class ScheduleFormViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ScheduleModel
|
||||
{
|
||||
Enabled = true,
|
||||
IntervalMinutes = 1440,
|
||||
PrePurge = true,
|
||||
ReIndex = false
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ScheduleFormViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Enabled.ShouldBeTrue();
|
||||
sut.IntervalMinutes.ShouldBe(1440);
|
||||
sut.PrePurge.ShouldBeTrue();
|
||||
sut.ReIndex.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModelAndInvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ScheduleModel();
|
||||
var changedInvoked = false;
|
||||
var sut = new ScheduleFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.IntervalMinutes = 120;
|
||||
|
||||
// Assert
|
||||
model.IntervalMinutes.ShouldBe(120);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ScheduleModel();
|
||||
var sut = new ScheduleFormViewModel(model, () => { });
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(ScheduleFormViewModel.PrePurge))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.PrePurge = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameValue_DoesNotInvokeOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ScheduleModel { Enabled = true };
|
||||
var changedInvoked = false;
|
||||
var sut = new ScheduleFormViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act - set same value
|
||||
sut.Enabled = true;
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeFalse();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.Services.SecureStore;
|
||||
using JdeScoping.ConfigManager.ViewModels;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
@@ -38,7 +39,7 @@ public class MainWindowViewModelTests
|
||||
|
||||
_validationService.ValidateAppSettings(Arg.Any<ConfigModel>())
|
||||
.Returns(new ValidationResult());
|
||||
_validationService.ValidatePipelines(Arg.Any<PipelinesConfigModel>())
|
||||
_validationService.ValidatePipelines(Arg.Any<Dictionary<string, EtlPipelineConfig>>())
|
||||
.Returns(new ValidationResult());
|
||||
}
|
||||
|
||||
@@ -167,15 +168,13 @@ public class MainWindowViewModelTests
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new PipelinesConfigModel
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
Pipelines = new Dictionary<string, PipelineModel>
|
||||
["WorkOrders"] = new EtlPipelineConfig
|
||||
{
|
||||
["WorkOrders"] = new PipelineModel
|
||||
{
|
||||
Source = new PipelineSource { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new PipelineDestination { Table = "WorkOrder_Curr" }
|
||||
}
|
||||
Name = "WorkOrders",
|
||||
Source = new SourceElement { Connection = "jde", Query = "SELECT * FROM WO" },
|
||||
Destination = new DestinationElement { Table = "WorkOrder_Curr" }
|
||||
}
|
||||
};
|
||||
var sut = CreateViewModel();
|
||||
@@ -295,13 +294,10 @@ public class MainWindowViewModelTests
|
||||
{
|
||||
// Arrange
|
||||
var config = new ConfigModel();
|
||||
var pipelines = new PipelinesConfigModel
|
||||
var pipelines = new Dictionary<string, EtlPipelineConfig>
|
||||
{
|
||||
Pipelines = new Dictionary<string, PipelineModel>
|
||||
{
|
||||
["Pipeline1"] = new PipelineModel(),
|
||||
["Pipeline2"] = new PipelineModel()
|
||||
}
|
||||
["Pipeline1"] = new EtlPipelineConfig { Name = "Pipeline1" },
|
||||
["Pipeline2"] = new EtlPipelineConfig { Name = "Pipeline2" }
|
||||
};
|
||||
var sut = CreateViewModel();
|
||||
|
||||
|
||||
+50
-28
@@ -1,26 +1,42 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.ViewModels.PipelineSteps;
|
||||
using JdeScoping.DataSync.Configuration;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels;
|
||||
|
||||
public class RegexTransformerViewModelTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
private static TransformElement CreateElement(object config)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(config, JsonOptions);
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
return new TransformElement
|
||||
{
|
||||
TransformType = "Regex",
|
||||
Config = doc.RootElement.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FromModel_LoadsAllProperties()
|
||||
public void Constructor_FromElement_LoadsAllProperties()
|
||||
{
|
||||
// Arrange
|
||||
var model = new TransformerModel
|
||||
var element = CreateElement(new
|
||||
{
|
||||
Type = "Regex",
|
||||
ColumnName = "BatchID",
|
||||
Pattern = "^IIS_",
|
||||
Replacement = "",
|
||||
IgnoreCase = true,
|
||||
NonMatchBehavior = NonMatchBehavior.ReturnEmpty
|
||||
};
|
||||
columnName = "BatchID",
|
||||
pattern = "^IIS_",
|
||||
replacement = "",
|
||||
ignoreCase = true,
|
||||
nonMatchBehavior = "ReturnEmpty"
|
||||
});
|
||||
|
||||
// Act
|
||||
var vm = new RegexTransformerViewModel(model, () => { });
|
||||
var vm = new RegexTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("BatchID", vm.ColumnName);
|
||||
@@ -32,19 +48,17 @@ public class RegexTransformerViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_FromModel_MatchExtractMode_WhenReplacementNull()
|
||||
public void Constructor_FromElement_MatchExtractMode_WhenReplacementNull()
|
||||
{
|
||||
// Arrange
|
||||
var model = new TransformerModel
|
||||
var element = CreateElement(new
|
||||
{
|
||||
Type = "Regex",
|
||||
ColumnName = "Code",
|
||||
Pattern = @"(\d+)",
|
||||
Replacement = null
|
||||
};
|
||||
columnName = "Code",
|
||||
pattern = @"(\d+)"
|
||||
});
|
||||
|
||||
// Act
|
||||
var vm = new RegexTransformerViewModel(model, () => { });
|
||||
var vm = new RegexTransformerViewModel(element, () => { });
|
||||
|
||||
// Assert
|
||||
Assert.False(vm.IsFindReplaceMode);
|
||||
@@ -66,15 +80,18 @@ public class RegexTransformerViewModelTests
|
||||
};
|
||||
|
||||
// Act
|
||||
var model = vm.ToModel();
|
||||
var element = vm.ToModel();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Regex", model.Type);
|
||||
Assert.Equal("BatchID", model.ColumnName);
|
||||
Assert.Equal("^IIS_", model.Pattern);
|
||||
Assert.Equal("", model.Replacement);
|
||||
Assert.True(model.IgnoreCase);
|
||||
Assert.Equal(NonMatchBehavior.KeepOriginal, model.NonMatchBehavior);
|
||||
Assert.Equal("Regex", element.TransformType);
|
||||
Assert.True(element.Config.HasValue);
|
||||
|
||||
// Parse the config to verify
|
||||
var config = element.Config!.Value;
|
||||
Assert.Equal("BatchID", config.GetProperty("columnName").GetString());
|
||||
Assert.Equal("^IIS_", config.GetProperty("pattern").GetString());
|
||||
Assert.Equal("", config.GetProperty("replacement").GetString());
|
||||
Assert.True(config.GetProperty("ignoreCase").GetBoolean());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -89,10 +106,15 @@ public class RegexTransformerViewModelTests
|
||||
};
|
||||
|
||||
// Act
|
||||
var model = vm.ToModel();
|
||||
var element = vm.ToModel();
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.Replacement); // null indicates Match & Extract mode
|
||||
Assert.True(element.Config.HasValue);
|
||||
var config = element.Config!.Value;
|
||||
|
||||
// replacement should be null in Match & Extract mode
|
||||
Assert.True(config.TryGetProperty("replacement", out var replacement));
|
||||
Assert.Equal(JsonValueKind.Null, replacement.ValueKind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user