docs: add XML documentation to NEW solution src files

Fix 173 of 175 documentation issues across 48 files using CommentChecker,
adding missing <summary>, <param>, and <inheritdoc /> tags to public APIs.
This commit is contained in:
Joseph Doherty
2026-01-27 06:19:20 -05:00
parent bfc1c8064a
commit 227a749cdf
53 changed files with 667 additions and 124 deletions
@@ -47,6 +47,7 @@ public partial class DataSyncRequests : ComponentBase
/// <summary> /// <summary>
/// Loads initial data when the component is initialized. /// Loads initial data when the component is initialized.
/// </summary> /// </summary>
/// <inheritdoc />
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await LoadPipelinesAsync(); await LoadPipelinesAsync();
@@ -64,18 +64,20 @@ internal sealed class TransformingDataReader : IDataReader
public bool IsDBNull(int i) => _transformer.IsDBNull(i, _source); public bool IsDBNull(int i) => _transformer.IsDBNull(i, _source);
/// <summary> /// <summary>
/// Gets the value of the column at the specified ordinal, applying transformations. /// Gets the value of the column at the specified ordinal.
/// </summary> /// </summary>
/// <param name="i">The zero-based column ordinal.</param> public object this[int i]
/// <returns>The transformed value of the column.</returns> {
public object this[int i] => GetValue(i); get => GetValue(i);
}
/// <summary> /// <summary>
/// Gets the value of the column with the specified name, applying transformations. /// Gets the value of the column with the specified name.
/// </summary> /// </summary>
/// <param name="name">The name of the column.</param> public object this[string name]
/// <returns>The transformed value of the column.</returns> {
public object this[string name] => GetValue(GetOrdinal(name)); get => GetValue(GetOrdinal(name));
}
// Row navigation - delegated directly to source // Row navigation - delegated directly to source
/// <summary> /// <summary>
@@ -63,6 +63,9 @@ public class DataUpdateTask
/// <summary> /// <summary>
/// Creates a DataUpdateTask from an EtlPipelineConfig. /// Creates a DataUpdateTask from an EtlPipelineConfig.
/// </summary> /// </summary>
/// <param name="pipeline">The ETL pipeline configuration.</param>
/// <param name="updateType">The type of update (Mass, Daily, or Hourly).</param>
/// <param name="minimumDt">Optional minimum datetime for incremental updates.</param>
public static DataUpdateTask FromPipeline( public static DataUpdateTask FromPipeline(
EtlPipelineConfig pipeline, EtlPipelineConfig pipeline,
UpdateTypes updateType, UpdateTypes updateType,
@@ -46,6 +46,10 @@ public class PipelineRegistry : IPipelineRegistry
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PipelineRegistry"/> class. /// Initializes a new instance of the <see cref="PipelineRegistry"/> class.
/// </summary> /// </summary>
/// <param name="options">Data sync configuration options.</param>
/// <param name="validator">Pipeline configuration validator.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
/// <param name="environment">Host environment information.</param>
public PipelineRegistry( public PipelineRegistry(
IOptions<DataSyncOptions> options, IOptions<DataSyncOptions> options,
IPipelineValidator validator, IPipelineValidator validator,
@@ -19,6 +19,10 @@ public class PipelineRegistryInitializer : IHostedService
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PipelineRegistryInitializer"/> class. /// Initializes a new instance of the <see cref="PipelineRegistryInitializer"/> class.
/// </summary> /// </summary>
/// <param name="registry">The pipeline registry to initialize.</param>
/// <param name="options">Data sync configuration options.</param>
/// <param name="lifetime">Application lifetime management.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
public PipelineRegistryInitializer( public PipelineRegistryInitializer(
IPipelineRegistry registry, IPipelineRegistry registry,
IOptions<DataSyncOptions> options, IOptions<DataSyncOptions> options,
@@ -16,10 +16,6 @@
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" /> <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="ConfigValidation\" />
</ItemGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
@@ -20,6 +20,12 @@ public class ConnectionStringValidator : IConfigurationValidator
/// <inheritdoc /> /// <inheritdoc />
public string Name => "ConnectionStrings"; public string Name => "ConnectionStrings";
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionStringValidator"/> class.
/// </summary>
/// <param name="configuration">Application configuration.</param>
/// <param name="secureStore">Service for retrieving encrypted connection strings.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
public ConnectionStringValidator( public ConnectionStringValidator(
IConfiguration configuration, IConfiguration configuration,
ISecureStoreService secureStore, ISecureStoreService secureStore,
@@ -19,6 +19,11 @@ public class LdapOptionsValidator : IConfigurationValidator
/// <inheritdoc /> /// <inheritdoc />
public string Name => "LdapOptions"; public string Name => "LdapOptions";
/// <summary>
/// Initializes a new instance of the <see cref="LdapOptionsValidator"/> class.
/// </summary>
/// <param name="options">LDAP configuration options.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
public LdapOptionsValidator( public LdapOptionsValidator(
IOptions<LdapOptions> options, IOptions<LdapOptions> options,
ILogger<LdapOptionsValidator> logger) ILogger<LdapOptionsValidator> logger)
@@ -21,6 +21,12 @@ public class SecureStoreValidator : IConfigurationValidator
/// <inheritdoc /> /// <inheritdoc />
public string Name => "SecureStore"; public string Name => "SecureStore";
/// <summary>
/// Initializes a new instance of the <see cref="SecureStoreValidator"/> class.
/// </summary>
/// <param name="secureStore">Service for retrieving encrypted secrets.</param>
/// <param name="options">SecureStore configuration options.</param>
/// <param name="logger">Logger instance for diagnostics.</param>
public SecureStoreValidator( public SecureStoreValidator(
ISecureStoreService secureStore, ISecureStoreService secureStore,
IOptions<SecureStoreOptions> options, IOptions<SecureStoreOptions> options,
@@ -3,9 +3,10 @@
xmlns:converters="clr-namespace:JdeScoping.ConfigManager.Converters" xmlns:converters="clr-namespace:JdeScoping.ConfigManager.Converters"
x:Class="JdeScoping.ConfigManager.App" x:Class="JdeScoping.ConfigManager.App"
RequestedThemeVariant="Dark"> RequestedThemeVariant="Dark">
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
</Application.Styles> <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
</Application.Styles>
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<!-- Converters --> <!-- Converters -->
@@ -38,6 +38,11 @@ public class ProviderToVisibilityConverter : IValueConverter
/// <summary> /// <summary>
/// Converts a ConnectionProvider to a boolean indicating visibility. /// Converts a ConnectionProvider to a boolean indicating visibility.
/// </summary> /// </summary>
/// <param name="value">The ConnectionProvider value to convert.</param>
/// <param name="targetType">The target type (bool).</param>
/// <param name="parameter">Unused converter parameter.</param>
/// <param name="culture">The culture for conversion.</param>
/// <returns>True if the value matches the target provider; otherwise, false.</returns>
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value is ConnectionProvider provider) if (value is ConnectionProvider provider)
@@ -50,6 +55,11 @@ public class ProviderToVisibilityConverter : IValueConverter
/// <summary> /// <summary>
/// Not implemented - this is a one-way converter. /// Not implemented - this is a one-way converter.
/// </summary> /// </summary>
/// <param name="value">The value to convert back.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Unused converter parameter.</param>
/// <param name="culture">The culture for conversion.</param>
/// <exception cref="NotImplementedException">This converter does not support reverse conversion.</exception>
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@@ -5,25 +5,74 @@ namespace JdeScoping.ConfigManager.Models;
/// </summary> /// </summary>
public class ConnectionStringEntry public class ConnectionStringEntry
{ {
/// <summary>
/// Gets or sets the name of this connection string entry.
/// </summary>
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the database provider type for this connection.
/// </summary>
public ConnectionProvider Provider { get; set; } = ConnectionProvider.Generic; public ConnectionProvider Provider { get; set; } = ConnectionProvider.Generic;
// SqlServer fields /// <summary>
/// Gets or sets the SQL Server server name or address.
/// </summary>
public string? Server { get; set; } public string? Server { get; set; }
/// <summary>
/// Gets or sets the SQL Server database name.
/// </summary>
public string? Database { get; set; } public string? Database { get; set; }
/// <summary>
/// Gets or sets the user ID for database authentication.
/// </summary>
public string? UserId { get; set; } public string? UserId { get; set; }
/// <summary>
/// Gets or sets the password for database authentication.
/// </summary>
public string? Password { get; set; } public string? Password { get; set; }
/// <summary>
/// Gets or sets the encryption setting for SQL Server connection ("True" or "False").
/// </summary>
public string Encrypt { get; set; } = "True"; public string Encrypt { get; set; } = "True";
/// <summary>
/// Gets or sets a value indicating whether to trust server certificate for SQL Server.
/// </summary>
public bool TrustServerCertificate { get; set; } public bool TrustServerCertificate { get; set; }
/// <summary>
/// Gets or sets the connection timeout in seconds for SQL Server.
/// </summary>
public int ConnectionTimeout { get; set; } = 30; public int ConnectionTimeout { get; set; } = 30;
/// <summary>
/// Gets or sets the application name for SQL Server connection identification.
/// </summary>
public string? ApplicationName { get; set; } public string? ApplicationName { get; set; }
// Oracle fields /// <summary>
/// Gets or sets the Oracle server host name or address.
/// </summary>
public string? Host { get; set; } public string? Host { get; set; }
/// <summary>
/// Gets or sets the Oracle server port number.
/// </summary>
public int Port { get; set; } = 1521; public int Port { get; set; } = 1521;
/// <summary>
/// Gets or sets the Oracle service name.
/// </summary>
public string? ServiceName { get; set; } public string? ServiceName { get; set; }
// Generic fields /// <summary>
/// Gets or sets the raw connection string for Generic provider type.
/// </summary>
public string? RawConnectionString { get; set; } public string? RawConnectionString { get; set; }
/// <summary> /// <summary>
@@ -9,5 +9,8 @@ namespace JdeScoping.ConfigManager.Models;
[JsonConverter(typeof(ConnectionStringsSectionConverter))] [JsonConverter(typeof(ConnectionStringsSectionConverter))]
public class ConnectionStringsSection public class ConnectionStringsSection
{ {
/// <summary>
/// Gets or sets the list of connection string entries.
/// </summary>
public List<ConnectionStringEntry> Entries { get; set; } = new(); public List<ConnectionStringEntry> Entries { get; set; } = new();
} }
@@ -19,6 +19,7 @@ namespace JdeScoping.ConfigManager.Models;
/// </remarks> /// </remarks>
public class ConnectionStringsSectionConverter : JsonConverter<ConnectionStringsSection> public class ConnectionStringsSectionConverter : JsonConverter<ConnectionStringsSection>
{ {
/// <inheritdoc />
public override ConnectionStringsSection? Read( public override ConnectionStringsSection? Read(
ref Utf8JsonReader reader, ref Utf8JsonReader reader,
Type typeToConvert, Type typeToConvert,
@@ -61,18 +62,16 @@ public class ConnectionStringsSectionConverter : JsonConverter<ConnectionStrings
? reader.GetString() ? reader.GetString()
: null; : null;
if (!string.IsNullOrEmpty(connectionString)) var entry = ParseConnectionString(propertyName, connectionString ?? string.Empty);
{ section.Entries.Add(entry);
var entry = ParseConnectionString(propertyName, connectionString); }
section.Entries.Add(entry); }
} }
}
}
}
return section; return section;
} }
/// <inheritdoc />
public override void Write( public override void Write(
Utf8JsonWriter writer, Utf8JsonWriter writer,
ConnectionStringsSection value, ConnectionStringsSection value,
@@ -95,23 +94,57 @@ public class ConnectionStringsSectionConverter : JsonConverter<ConnectionStrings
/// <summary> /// <summary>
/// Parses a connection string and attempts to detect the provider type. /// Parses a connection string and attempts to detect the provider type.
/// </summary> /// </summary>
private static ConnectionStringEntry ParseConnectionString(string name, string connectionString) internal static ConnectionStringEntry ParseConnectionString(string name, string connectionString)
{ {
var entry = new ConnectionStringEntry var entry = new ConnectionStringEntry
{ {
Name = name, Name = name,
RawConnectionString = connectionString RawConnectionString = connectionString
}; };
// Try to detect provider and parse structured fields // Try to detect provider and parse structured fields
var parts = ParseConnectionStringParts(connectionString); var parts = ParseConnectionStringParts(connectionString);
// Detect Oracle first (Data Source with host:port/service pattern) // Detect Oracle using HOST/Service Name/Port pattern (DDTek.Oracle style)
if (parts.TryGetValue("data source", out var dataSource) && var hasHost = parts.TryGetValue("host", out var host);
IsOracleDataSource(dataSource)) var hasServiceName = parts.TryGetValue("service name", out var serviceName);
{ var hasPort = parts.TryGetValue("port", out var portText);
entry.Provider = ConnectionProvider.Oracle;
ParseOracleDataSource(entry, dataSource); if (hasHost || hasServiceName || hasPort)
{
entry.Provider = ConnectionProvider.Oracle;
if (hasHost && !string.IsNullOrEmpty(host))
{
entry.Host = host;
}
if (hasServiceName && !string.IsNullOrEmpty(serviceName))
{
entry.ServiceName = serviceName;
}
if (hasPort && !string.IsNullOrEmpty(portText) && int.TryParse(portText, out var port))
{
entry.Port = port;
}
if (parts.TryGetValue("user id", out var oraUserId))
{
entry.UserId = oraUserId;
}
if (parts.TryGetValue("password", out var oraPassword))
{
entry.Password = oraPassword;
}
}
// Detect Oracle first (Data Source with host:port/service pattern)
else if (parts.TryGetValue("data source", out var dataSource) &&
IsOracleDataSource(dataSource))
{
entry.Provider = ConnectionProvider.Oracle;
ParseOracleDataSource(entry, dataSource);
if (parts.TryGetValue("user id", out var oraUserId)) if (parts.TryGetValue("user id", out var oraUserId))
{ {
@@ -179,8 +212,27 @@ public class ConnectionStringsSectionConverter : JsonConverter<ConnectionStrings
entry.Provider = ConnectionProvider.Generic; entry.Provider = ConnectionProvider.Generic;
} }
return entry; return entry;
} }
internal static void ApplyConnectionString(ConnectionStringEntry entry, string connectionString)
{
var parsed = ParseConnectionString(entry.Name, connectionString);
entry.Provider = parsed.Provider;
entry.Server = parsed.Server;
entry.Database = parsed.Database;
entry.UserId = parsed.UserId;
entry.Password = parsed.Password;
entry.Encrypt = parsed.Encrypt;
entry.TrustServerCertificate = parsed.TrustServerCertificate;
entry.ConnectionTimeout = parsed.ConnectionTimeout;
entry.ApplicationName = parsed.ApplicationName;
entry.Host = parsed.Host;
entry.Port = parsed.Port;
entry.ServiceName = parsed.ServiceName;
entry.RawConnectionString = parsed.RawConnectionString;
}
/// <summary> /// <summary>
/// Parses a connection string into key-value pairs. /// Parses a connection string into key-value pairs.
@@ -11,6 +11,13 @@ public class ConnectionTestService : IConnectionTestService
{ {
private const int ConnectionTimeoutSeconds = 10; private const int ConnectionTimeoutSeconds = 10;
/// <summary>
/// Tests a database connection asynchronously.
/// </summary>
/// <param name="connectionString">The connection string to test.</param>
/// <param name="provider">The database provider type.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A ConnectionTestResult indicating success or failure of the test.</returns>
public async Task<ConnectionTestResult> TestConnectionAsync( public async Task<ConnectionTestResult> TestConnectionAsync(
string connectionString, string connectionString,
ConnectionProvider provider, ConnectionProvider provider,
@@ -7,8 +7,19 @@ namespace JdeScoping.ConfigManager.Services;
/// </summary> /// </summary>
public class ConnectionTestResult public class ConnectionTestResult
{ {
/// <summary>
/// Gets a value indicating whether the connection test was successful.
/// </summary>
public bool Success { get; init; } public bool Success { get; init; }
/// <summary>
/// Gets the message describing the result of the connection test.
/// </summary>
public string Message { get; init; } = string.Empty; public string Message { get; init; } = string.Empty;
/// <summary>
/// Gets the elapsed time of the connection test operation.
/// </summary>
public TimeSpan? Duration { get; init; } public TimeSpan? Duration { get; init; }
} }
@@ -17,5 +28,12 @@ public class ConnectionTestResult
/// </summary> /// </summary>
public interface IConnectionTestService public interface IConnectionTestService
{ {
/// <summary>
/// Tests a database connection asynchronously.
/// </summary>
/// <param name="connectionString">The connection string to test.</param>
/// <param name="provider">The database provider type.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A ConnectionTestResult indicating success or failure of the test.</returns>
Task<ConnectionTestResult> TestConnectionAsync(string connectionString, ConnectionProvider provider, CancellationToken cancellationToken = default); Task<ConnectionTestResult> TestConnectionAsync(string connectionString, ConnectionProvider provider, CancellationToken cancellationToken = default);
} }
@@ -11,6 +11,10 @@ public class DiffPreviewDialogViewModel : ViewModelBase
{ {
private bool _result; private bool _result;
/// <summary>
/// Initializes a new instance of the <see cref="DiffPreviewDialogViewModel"/> class.
/// </summary>
/// <param name="diff">The diff result containing the changes to preview.</param>
public DiffPreviewDialogViewModel(DiffResult diff) public DiffPreviewDialogViewModel(DiffResult diff)
{ {
ArgumentNullException.ThrowIfNull(diff); ArgumentNullException.ThrowIfNull(diff);
@@ -25,19 +29,48 @@ public class DiffPreviewDialogViewModel : ViewModelBase
CancelCommand = new RelayCommand(() => { Result = false; RequestClose?.Invoke(); }); CancelCommand = new RelayCommand(() => { Result = false; RequestClose?.Invoke(); });
} }
/// <summary>
/// Gets the collection of diff lines to display in the preview.
/// </summary>
public ObservableCollection<DiffLineViewModel> Lines { get; } public ObservableCollection<DiffLineViewModel> Lines { get; }
/// <summary>
/// Gets the number of lines inserted in the diff.
/// </summary>
public int Insertions { get; } public int Insertions { get; }
/// <summary>
/// Gets the number of lines deleted in the diff.
/// </summary>
public int Deletions { get; } public int Deletions { get; }
/// <summary>
/// Gets a value indicating whether the diff contains any changes.
/// </summary>
public bool HasChanges { get; } public bool HasChanges { get; }
/// <summary>
/// Gets or sets the dialog result (true if saved, false if canceled).
/// </summary>
public bool Result public bool Result
{ {
get => _result; get => _result;
private set => SetProperty(ref _result, value); private set => SetProperty(ref _result, value);
} }
/// <summary>
/// Gets the command to save the diff changes and close the dialog.
/// </summary>
public ICommand SaveCommand { get; } public ICommand SaveCommand { get; }
/// <summary>
/// Gets the command to cancel and close the dialog without saving.
/// </summary>
public ICommand CancelCommand { get; } public ICommand CancelCommand { get; }
/// <summary>
/// Gets or sets the action to request dialog closure.
/// </summary>
public Action? RequestClose { get; set; } public Action? RequestClose { get; set; }
} }
@@ -46,6 +79,10 @@ public class DiffPreviewDialogViewModel : ViewModelBase
/// </summary> /// </summary>
public class DiffLineViewModel public class DiffLineViewModel
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DiffLineViewModel"/> class.
/// </summary>
/// <param name="line">The diff line data to represent.</param>
public DiffLineViewModel(DiffLine line) public DiffLineViewModel(DiffLine line)
{ {
OldLineNumber = line.OldLineNumber?.ToString() ?? ""; OldLineNumber = line.OldLineNumber?.ToString() ?? "";
@@ -66,10 +103,33 @@ public class DiffLineViewModel
}; };
} }
/// <summary>
/// Gets the original line number (empty string if not applicable).
/// </summary>
public string OldLineNumber { get; } public string OldLineNumber { get; }
/// <summary>
/// Gets the new line number (empty string if not applicable).
/// </summary>
public string NewLineNumber { get; } public string NewLineNumber { get; }
/// <summary>
/// Gets the text content of the diff line.
/// </summary>
public string Text { get; } public string Text { get; }
/// <summary>
/// Gets the type of diff line (added, removed, or context).
/// </summary>
public DiffLineType Type { get; } public DiffLineType Type { get; }
/// <summary>
/// Gets the background color for the diff line visualization.
/// </summary>
public string Background { get; } public string Background { get; }
/// <summary>
/// Gets the border color for the diff line visualization.
/// </summary>
public string BorderColor { get; } public string BorderColor { get; }
} }
@@ -10,6 +10,11 @@ public class AuthFormViewModel : ViewModelBase
private readonly AuthSection _model; private readonly AuthSection _model;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="AuthFormViewModel"/> class.
/// </summary>
/// <param name="model">The auth section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public AuthFormViewModel(AuthSection model, Action onChanged) public AuthFormViewModel(AuthSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -52,11 +52,10 @@ public class ConnectionStringsFormViewModel : ViewModelBase
: null; : null;
// Update entry's RawConnectionString with SecureStore value if available // Update entry's RawConnectionString with SecureStore value if available
if (!string.IsNullOrEmpty(secureStoreValue)) if (!string.IsNullOrEmpty(secureStoreValue))
{ {
entry.RawConnectionString = secureStoreValue; ConnectionStringsSectionConverter.ApplyConnectionString(entry, secureStoreValue);
entry.Provider = ConnectionProvider.Generic; // Use Generic since we have the full string }
}
Connections.Add(new ConnectionStringEntryViewModel(entry, OnEntryChanged)); Connections.Add(new ConnectionStringEntryViewModel(entry, OnEntryChanged));
} }
@@ -10,6 +10,11 @@ public class DataAccessFormViewModel : ViewModelBase
private readonly DataAccessSection _model; private readonly DataAccessSection _model;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="DataAccessFormViewModel"/> class.
/// </summary>
/// <param name="model">The data access section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public DataAccessFormViewModel(DataAccessSection model, Action onChanged) public DataAccessFormViewModel(DataAccessSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -10,6 +10,11 @@ public class DataSyncFormViewModel : ViewModelBase
private readonly DataSyncSection _model; private readonly DataSyncSection _model;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="DataSyncFormViewModel"/> class.
/// </summary>
/// <param name="model">The data sync section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public DataSyncFormViewModel(DataSyncSection model, Action onChanged) public DataSyncFormViewModel(DataSyncSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -21,6 +21,11 @@ public class ExcelExportFormViewModel : ViewModelBase
.ToList() .ToList()
.AsReadOnly(); .AsReadOnly();
/// <summary>
/// Initializes a new instance of the <see cref="ExcelExportFormViewModel"/> class.
/// </summary>
/// <param name="model">The excel export section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public ExcelExportFormViewModel(ExcelExportSection model, Action onChanged) public ExcelExportFormViewModel(ExcelExportSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -10,6 +10,11 @@ public class LdapFormViewModel : ViewModelBase
private readonly LdapSection _model; private readonly LdapSection _model;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="LdapFormViewModel"/> class.
/// </summary>
/// <param name="model">The LDAP section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public LdapFormViewModel(LdapSection model, Action onChanged) public LdapFormViewModel(LdapSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -17,6 +17,14 @@ public class PipelineEditorViewModel : ViewModelBase
private PipelineStepViewModelBase? _selectedStep; private PipelineStepViewModelBase? _selectedStep;
private object? _selectedStepEditor; private object? _selectedStepEditor;
/// <summary>
/// Initializes a new instance of the <see cref="PipelineEditorViewModel"/> class.
/// </summary>
/// <param name="name">The pipeline name.</param>
/// <param name="model">The pipeline configuration model.</param>
/// <param name="availableConnections">List of available connection names from configuration.</param>
/// <param name="dialogService">Service for showing dialogs.</param>
/// <param name="onChanged">Callback invoked when the pipeline configuration changes.</param>
public PipelineEditorViewModel(string name, EtlPipelineConfig model, IReadOnlyList<string> availableConnections, IDialogService dialogService, Action onChanged) public PipelineEditorViewModel(string name, EtlPipelineConfig model, IReadOnlyList<string> availableConnections, IDialogService dialogService, Action onChanged)
{ {
Name = name ?? throw new ArgumentNullException(nameof(name)); Name = name ?? throw new ArgumentNullException(nameof(name));
@@ -318,14 +326,49 @@ public class PipelineEditorViewModel : ViewModelBase
} }
// Commands // Commands
/// <summary>
/// Gets the command to add a pre-script step.
/// </summary>
public ICommand AddPreScriptCommand { get; } public ICommand AddPreScriptCommand { get; }
/// <summary>
/// Gets the command to add a transformer step.
/// </summary>
public ICommand AddTransformerCommand { get; } public ICommand AddTransformerCommand { get; }
/// <summary>
/// Gets the command to add a post-script step.
/// </summary>
public ICommand AddPostScriptCommand { get; } public ICommand AddPostScriptCommand { get; }
/// <summary>
/// Gets the command to remove a pipeline step.
/// </summary>
public ICommand RemoveStepCommand { get; } public ICommand RemoveStepCommand { get; }
/// <summary>
/// Gets the command to delete the currently selected step.
/// </summary>
public ICommand DeleteSelectedStepCommand { get; } public ICommand DeleteSelectedStepCommand { get; }
/// <summary>
/// Gets the command to move a step up in its collection.
/// </summary>
public ICommand MoveStepUpCommand { get; } public ICommand MoveStepUpCommand { get; }
/// <summary>
/// Gets the command to move a step down in its collection.
/// </summary>
public ICommand MoveStepDownCommand { get; } public ICommand MoveStepDownCommand { get; }
/// <summary>
/// Gets the command to move the selected step up.
/// </summary>
public ICommand MoveSelectedStepUpCommand { get; } public ICommand MoveSelectedStepUpCommand { get; }
/// <summary>
/// Gets the command to move the selected step down.
/// </summary>
public ICommand MoveSelectedStepDownCommand { get; } public ICommand MoveSelectedStepDownCommand { get; }
/// <summary> /// <summary>
@@ -333,10 +376,11 @@ public class PipelineEditorViewModel : ViewModelBase
/// </summary> /// </summary>
public IReadOnlyList<string> AvailableTransformerTypes => TransformerFactory.AvailableTypes; public IReadOnlyList<string> AvailableTransformerTypes => TransformerFactory.AvailableTypes;
/// <summary>
/// Property to track selected transformer type for adding.
/// </summary>
private string? _selectedTransformerType; private string? _selectedTransformerType;
/// <summary>
/// Gets or sets the selected transformer type for adding a new transformer.
/// </summary>
public string? SelectedTransformerType public string? SelectedTransformerType
{ {
get => _selectedTransformerType; get => _selectedTransformerType;
@@ -438,6 +482,7 @@ public class PipelineEditorViewModel : ViewModelBase
/// <summary> /// <summary>
/// Adds a specific transformer type. /// Adds a specific transformer type.
/// </summary> /// </summary>
/// <param name="typeName">The name of the transformer type to add.</param>
public void AddTransformerOfType(string typeName) public void AddTransformerOfType(string typeName)
{ {
var vm = TransformerFactory.CreateNew(typeName, () => var vm = TransformerFactory.CreateNew(typeName, () =>
@@ -10,6 +10,11 @@ public class SearchFormViewModel : ViewModelBase
private readonly SearchSection _model; private readonly SearchSection _model;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="SearchFormViewModel"/> class.
/// </summary>
/// <param name="model">The search section model to edit.</param>
/// <param name="onChanged">The callback to invoke when configuration changes.</param>
public SearchFormViewModel(SearchSection model, Action onChanged) public SearchFormViewModel(SearchSection model, Action onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -194,6 +194,7 @@ public class MainWindowViewModel : ViewModelBase
/// <param name="secureStoreManager">Service for managing encrypted secret stores.</param> /// <param name="secureStoreManager">Service for managing encrypted secret stores.</param>
/// <param name="clipboardService">Service for clipboard operations.</param> /// <param name="clipboardService">Service for clipboard operations.</param>
/// <param name="runtimeValidationService">Service for runtime configuration validation.</param> /// <param name="runtimeValidationService">Service for runtime configuration validation.</param>
/// <param name="connectionTestService">Service for testing database connections.</param>
/// <param name="logger">Optional logger for recording view model activities.</param> /// <param name="logger">Optional logger for recording view model activities.</param>
public MainWindowViewModel( public MainWindowViewModel(
IFileSystem fileSystem, IFileSystem fileSystem,
@@ -271,6 +272,11 @@ public class MainWindowViewModel : ViewModelBase
/// </summary> /// </summary>
private class NullClipboardService : IClipboardService private class NullClipboardService : IClipboardService
{ {
/// <summary>
/// Sets text to clipboard (no-op for design-time).
/// </summary>
/// <param name="text">The text to set (ignored in this implementation).</param>
/// <returns>A completed task.</returns>
public Task SetTextAsync(string text) => Task.CompletedTask; public Task SetTextAsync(string text) => Task.CompletedTask;
} }
@@ -9,14 +9,26 @@ public class DestinationStepViewModel : PipelineStepViewModelBase
{ {
private readonly DestinationElement _model; private readonly DestinationElement _model;
/// <summary>
/// Initializes a new instance of the <see cref="DestinationStepViewModel"/> class.
/// </summary>
/// <param name="model">The destination configuration model.</param>
/// <param name="onChanged">Callback invoked when the destination configuration changes.</param>
public DestinationStepViewModel(DestinationElement model, Action onChanged) : base(onChanged) public DestinationStepViewModel(DestinationElement model, Action onChanged) : base(onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
} }
/// <inheritdoc />
public override PipelineStepType StepType => PipelineStepType.Destination; public override PipelineStepType StepType => PipelineStepType.Destination;
/// <inheritdoc />
public override string DisplayName => "Destination"; public override string DisplayName => "Destination";
/// <inheritdoc />
public override string Icon => "󰆼"; // mdi-database public override string Icon => "󰆼"; // mdi-database
/// <inheritdoc />
public override string Summary => !string.IsNullOrEmpty(Table) ? $"→ {Table}" : "(no table)"; public override string Summary => !string.IsNullOrEmpty(Table) ? $"→ {Table}" : "(no table)";
/// <summary> /// <summary>
@@ -23,6 +23,10 @@ public abstract class PipelineStepViewModelBase : ViewModelBase
private bool _isSelected; private bool _isSelected;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="PipelineStepViewModelBase"/> class.
/// </summary>
/// <param name="onChanged">Callback invoked when the step changes.</param>
protected PipelineStepViewModelBase(Action onChanged) protected PipelineStepViewModelBase(Action onChanged)
{ {
_onChanged = onChanged ?? throw new ArgumentNullException(nameof(onChanged)); _onChanged = onChanged ?? throw new ArgumentNullException(nameof(onChanged));
@@ -73,6 +77,11 @@ public class PreScriptStepViewModel : PipelineStepViewModelBase
{ {
private readonly ScriptElement _model; private readonly ScriptElement _model;
/// <summary>
/// Initializes a new instance of the <see cref="PreScriptStepViewModel"/> class with an existing script.
/// </summary>
/// <param name="model">The script element to wrap.</param>
/// <param name="onChanged">Callback invoked when the script changes.</param>
public PreScriptStepViewModel(ScriptElement model, Action onChanged) : base(onChanged) public PreScriptStepViewModel(ScriptElement model, Action onChanged) : base(onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -81,14 +90,19 @@ public class PreScriptStepViewModel : PipelineStepViewModelBase
/// <summary> /// <summary>
/// Creates a new pre-script with default values. /// Creates a new pre-script with default values.
/// </summary> /// </summary>
/// <param name="onChanged">Callback invoked when the script changes.</param>
public PreScriptStepViewModel(Action onChanged) : base(onChanged) public PreScriptStepViewModel(Action onChanged) : base(onChanged)
{ {
_model = new ScriptElement { Connection = "lotfinder", Script = string.Empty }; _model = new ScriptElement { Connection = "lotfinder", Script = string.Empty };
} }
/// <inheritdoc />
public override PipelineStepType StepType => PipelineStepType.PreScript; public override PipelineStepType StepType => PipelineStepType.PreScript;
/// <inheritdoc />
public override string DisplayName => "Pre-Script"; public override string DisplayName => "Pre-Script";
/// <inheritdoc />
public override string Icon => "󰯂"; // mdi-script-text public override string Icon => "󰯂"; // mdi-script-text
/// <inheritdoc />
public override string Summary => TruncateScript(_model.Script); public override string Summary => TruncateScript(_model.Script);
/// <summary> /// <summary>
@@ -146,6 +160,11 @@ public class PostScriptStepViewModel : PipelineStepViewModelBase
{ {
private readonly ScriptElement _model; private readonly ScriptElement _model;
/// <summary>
/// Initializes a new instance of the <see cref="PostScriptStepViewModel"/> class with an existing script.
/// </summary>
/// <param name="model">The script element to wrap.</param>
/// <param name="onChanged">Callback invoked when the script changes.</param>
public PostScriptStepViewModel(ScriptElement model, Action onChanged) : base(onChanged) public PostScriptStepViewModel(ScriptElement model, Action onChanged) : base(onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -154,14 +173,19 @@ public class PostScriptStepViewModel : PipelineStepViewModelBase
/// <summary> /// <summary>
/// Creates a new post-script with default values. /// Creates a new post-script with default values.
/// </summary> /// </summary>
/// <param name="onChanged">Callback invoked when the script changes.</param>
public PostScriptStepViewModel(Action onChanged) : base(onChanged) public PostScriptStepViewModel(Action onChanged) : base(onChanged)
{ {
_model = new ScriptElement { Connection = "lotfinder", Script = string.Empty }; _model = new ScriptElement { Connection = "lotfinder", Script = string.Empty };
} }
/// <inheritdoc />
public override PipelineStepType StepType => PipelineStepType.PostScript; public override PipelineStepType StepType => PipelineStepType.PostScript;
/// <inheritdoc />
public override string DisplayName => "Post-Script"; public override string DisplayName => "Post-Script";
/// <inheritdoc />
public override string Icon => "󰯂"; // mdi-script-text public override string Icon => "󰯂"; // mdi-script-text
/// <inheritdoc />
public override string Summary => TruncateScript(_model.Script); public override string Summary => TruncateScript(_model.Script);
/// <summary> /// <summary>
@@ -11,6 +11,12 @@ public class SourceStepViewModel : PipelineStepViewModelBase
{ {
private readonly SourceElement _model; private readonly SourceElement _model;
/// <summary>
/// Initializes a new instance of the <see cref="SourceStepViewModel"/> class.
/// </summary>
/// <param name="model">The source configuration model.</param>
/// <param name="availableConnections">List of available connection names from configuration.</param>
/// <param name="onChanged">Callback invoked when the source configuration changes.</param>
public SourceStepViewModel(SourceElement model, IReadOnlyList<string> availableConnections, Action onChanged) : base(onChanged) public SourceStepViewModel(SourceElement model, IReadOnlyList<string> availableConnections, Action onChanged) : base(onChanged)
{ {
_model = model ?? throw new ArgumentNullException(nameof(model)); _model = model ?? throw new ArgumentNullException(nameof(model));
@@ -33,9 +39,16 @@ public class SourceStepViewModel : PipelineStepViewModelBase
/// </summary> /// </summary>
public IReadOnlyList<string> AvailableConnections { get; } public IReadOnlyList<string> AvailableConnections { get; }
/// <inheritdoc />
public override PipelineStepType StepType => PipelineStepType.Source; public override PipelineStepType StepType => PipelineStepType.Source;
/// <inheritdoc />
public override string DisplayName => "Source"; public override string DisplayName => "Source";
/// <inheritdoc />
public override string Icon => "󰆼"; // mdi-database public override string Icon => "󰆼"; // mdi-database
/// <inheritdoc />
public override string Summary => $"{Connection}: {TruncateQuery(Query)}"; public override string Summary => $"{Connection}: {TruncateQuery(Query)}";
/// <summary> /// <summary>
@@ -121,6 +134,7 @@ public class SourceStepViewModel : PipelineStepViewModelBase
/// <summary> /// <summary>
/// Removes a parameter. /// Removes a parameter.
/// </summary> /// </summary>
/// <param name="parameter">The parameter view model to remove.</param>
public void RemoveParameter(ParameterViewModel parameter) public void RemoveParameter(ParameterViewModel parameter)
{ {
if (Parameters.Remove(parameter)) if (Parameters.Remove(parameter))
@@ -159,6 +173,12 @@ public class ParameterViewModel : ViewModelBase
private string? _value; private string? _value;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ParameterViewModel"/> class.
/// </summary>
/// <param name="key">The parameter key used in the dictionary.</param>
/// <param name="model">The parameter configuration model.</param>
/// <param name="onChanged">Callback invoked when the parameter changes.</param>
public ParameterViewModel(string key, ParameterElement model, Action onChanged) public ParameterViewModel(string key, ParameterElement model, Action onChanged)
{ {
_key = key; _key = key;
@@ -16,11 +16,17 @@ public abstract class TransformerStepViewModelBase : PipelineStepViewModelBase
PropertyNamingPolicy = JsonNamingPolicy.CamelCase PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}; };
/// <summary>
/// Initializes a new instance of the <see cref="TransformerStepViewModelBase"/> class.
/// </summary>
/// <param name="onChanged">Callback invoked when the transformer changes.</param>
protected TransformerStepViewModelBase(Action onChanged) : base(onChanged) protected TransformerStepViewModelBase(Action onChanged) : base(onChanged)
{ {
} }
/// <inheritdoc />
public override PipelineStepType StepType => PipelineStepType.Transformer; public override PipelineStepType StepType => PipelineStepType.Transformer;
/// <inheritdoc />
public override string Icon => "󰁖"; // mdi-cog-transfer public override string Icon => "󰁖"; // mdi-cog-transfer
/// <summary> /// <summary>
@@ -36,6 +42,8 @@ public abstract class TransformerStepViewModelBase : PipelineStepViewModelBase
/// <summary> /// <summary>
/// Helper to create a JsonElement from an object. /// Helper to create a JsonElement from an object.
/// </summary> /// </summary>
/// <param name="config">The object to serialize into a JsonElement.</param>
/// <returns>A JsonElement containing the serialized configuration.</returns>
protected static JsonElement CreateConfigElement(object config) protected static JsonElement CreateConfigElement(object config)
{ {
var json = JsonSerializer.Serialize(config, JsonOptions); var json = JsonSerializer.Serialize(config, JsonOptions);
@@ -51,6 +59,11 @@ public class ColumnDropTransformerViewModel : TransformerStepViewModelBase
{ {
private string _columnsText; private string _columnsText;
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDropTransformerViewModel"/> class with an existing configuration.
/// </summary>
/// <param name="element">The transform element containing the column configuration.</param>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public ColumnDropTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged) public ColumnDropTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged)
{ {
_columnsText = string.Empty; _columnsText = string.Empty;
@@ -65,13 +78,20 @@ public class ColumnDropTransformerViewModel : TransformerStepViewModelBase
} }
} }
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDropTransformerViewModel"/> class with default values.
/// </summary>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public ColumnDropTransformerViewModel(Action onChanged) : base(onChanged) public ColumnDropTransformerViewModel(Action onChanged) : base(onChanged)
{ {
_columnsText = string.Empty; _columnsText = string.Empty;
} }
/// <inheritdoc />
public override string TransformerType => "ColumnDrop"; public override string TransformerType => "ColumnDrop";
/// <inheritdoc />
public override string DisplayName => "Column Drop"; public override string DisplayName => "Column Drop";
/// <inheritdoc />
public override string Summary => GetColumnCount() > 0 ? $"Drop {GetColumnCount()} columns" : "No columns"; public override string Summary => GetColumnCount() > 0 ? $"Drop {GetColumnCount()} columns" : "No columns";
/// <summary> /// <summary>
@@ -103,6 +123,7 @@ public class ColumnDropTransformerViewModel : TransformerStepViewModelBase
return _columnsText.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Length; return _columnsText.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Length;
} }
/// <inheritdoc />
public override TransformElement ToModel() => new() public override TransformElement ToModel() => new()
{ {
TransformType = TransformerType, TransformType = TransformerType,
@@ -115,6 +136,11 @@ public class ColumnDropTransformerViewModel : TransformerStepViewModelBase
/// </summary> /// </summary>
public class ColumnRenameTransformerViewModel : TransformerStepViewModelBase public class ColumnRenameTransformerViewModel : TransformerStepViewModelBase
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ColumnRenameTransformerViewModel"/> class with an existing configuration.
/// </summary>
/// <param name="element">The transform element containing the mapping configuration.</param>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public ColumnRenameTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged) public ColumnRenameTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged)
{ {
Mappings = []; Mappings = [];
@@ -137,14 +163,21 @@ public class ColumnRenameTransformerViewModel : TransformerStepViewModelBase
AddMappingCommand = new RelayCommand(AddMapping); AddMappingCommand = new RelayCommand(AddMapping);
} }
/// <summary>
/// Initializes a new instance of the <see cref="ColumnRenameTransformerViewModel"/> class with default values.
/// </summary>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public ColumnRenameTransformerViewModel(Action onChanged) : base(onChanged) public ColumnRenameTransformerViewModel(Action onChanged) : base(onChanged)
{ {
Mappings = []; Mappings = [];
AddMappingCommand = new RelayCommand(AddMapping); AddMappingCommand = new RelayCommand(AddMapping);
} }
/// <inheritdoc />
public override string TransformerType => "ColumnRename"; public override string TransformerType => "ColumnRename";
/// <inheritdoc />
public override string DisplayName => "Column Rename"; public override string DisplayName => "Column Rename";
/// <inheritdoc />
public override string Summary => Mappings.Count > 0 ? $"Rename {Mappings.Count} columns" : "No mappings"; public override string Summary => Mappings.Count > 0 ? $"Rename {Mappings.Count} columns" : "No mappings";
/// <summary> /// <summary>
@@ -174,6 +207,7 @@ public class ColumnRenameTransformerViewModel : TransformerStepViewModelBase
/// <summary> /// <summary>
/// Removes a mapping. /// Removes a mapping.
/// </summary> /// </summary>
/// <param name="mapping">The column mapping to remove.</param>
public void RemoveMapping(ColumnMappingViewModel mapping) public void RemoveMapping(ColumnMappingViewModel mapping)
{ {
if (Mappings.Remove(mapping)) if (Mappings.Remove(mapping))
@@ -183,6 +217,7 @@ public class ColumnRenameTransformerViewModel : TransformerStepViewModelBase
} }
} }
/// <inheritdoc />
public override TransformElement ToModel() => new() public override TransformElement ToModel() => new()
{ {
TransformType = TransformerType, TransformType = TransformerType,
@@ -199,6 +234,12 @@ public class ColumnMappingViewModel : ViewModelBase
private string _newName; private string _newName;
private readonly Action _onChanged; private readonly Action _onChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ColumnMappingViewModel"/> class.
/// </summary>
/// <param name="oldName">The original column name.</param>
/// <param name="newName">The new column name.</param>
/// <param name="onChanged">Callback invoked when the mapping changes.</param>
public ColumnMappingViewModel(string oldName, string newName, Action onChanged) public ColumnMappingViewModel(string oldName, string newName, Action onChanged)
{ {
_oldName = oldName; _oldName = oldName;
@@ -242,6 +283,11 @@ public class JdeDateTransformerViewModel : TransformerStepViewModelBase
private string? _timeColumn; private string? _timeColumn;
private string? _outputColumn; private string? _outputColumn;
/// <summary>
/// Initializes a new instance of the <see cref="JdeDateTransformerViewModel"/> class from a TransformElement.
/// </summary>
/// <param name="element">The transform element containing date/time column configuration.</param>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public JdeDateTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged) public JdeDateTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged)
{ {
_dateColumn = null; _dateColumn = null;
@@ -259,6 +305,10 @@ public class JdeDateTransformerViewModel : TransformerStepViewModelBase
} }
} }
/// <summary>
/// Initializes a new instance of the <see cref="JdeDateTransformerViewModel"/> class with default values.
/// </summary>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public JdeDateTransformerViewModel(Action onChanged) : base(onChanged) public JdeDateTransformerViewModel(Action onChanged) : base(onChanged)
{ {
_dateColumn = null; _dateColumn = null;
@@ -266,9 +316,13 @@ public class JdeDateTransformerViewModel : TransformerStepViewModelBase
_outputColumn = null; _outputColumn = null;
} }
/// <inheritdoc />
public override string TransformerType => "JdeDate"; public override string TransformerType => "JdeDate";
/// <inheritdoc />
public override string DisplayName => "JDE Date Convert"; public override string DisplayName => "JDE Date Convert";
/// <inheritdoc />
public override string Icon => "󰃭"; // mdi-calendar public override string Icon => "󰃭"; // mdi-calendar
/// <inheritdoc />
public override string Summary => !string.IsNullOrEmpty(_outputColumn) ? $"→ {_outputColumn}" : "Configure..."; public override string Summary => !string.IsNullOrEmpty(_outputColumn) ? $"→ {_outputColumn}" : "Configure...";
/// <summary> /// <summary>
@@ -316,6 +370,7 @@ public class JdeDateTransformerViewModel : TransformerStepViewModelBase
} }
} }
/// <inheritdoc />
public override TransformElement ToModel() => new() public override TransformElement ToModel() => new()
{ {
TransformType = TransformerType, TransformType = TransformerType,
@@ -358,6 +413,11 @@ public class RegexTransformerViewModel : TransformerStepViewModelBase
private bool _hasTestError; private bool _hasTestError;
private string _testErrorMessage = string.Empty; private string _testErrorMessage = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="RegexTransformerViewModel"/> class from a TransformElement.
/// </summary>
/// <param name="element">The transform element containing regex configuration.</param>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public RegexTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged) public RegexTransformerViewModel(TransformElement element, Action onChanged) : base(onChanged)
{ {
_columnName = string.Empty; _columnName = string.Empty;
@@ -396,14 +456,22 @@ public class RegexTransformerViewModel : TransformerStepViewModelBase
TestPatternCommand = new RelayCommand(ExecuteTestPattern); TestPatternCommand = new RelayCommand(ExecuteTestPattern);
} }
/// <summary>
/// Initializes a new instance of the <see cref="RegexTransformerViewModel"/> class with default values.
/// </summary>
/// <param name="onChanged">Callback invoked when the configuration changes.</param>
public RegexTransformerViewModel(Action onChanged) : base(onChanged) public RegexTransformerViewModel(Action onChanged) : base(onChanged)
{ {
TestPatternCommand = new RelayCommand(ExecuteTestPattern); TestPatternCommand = new RelayCommand(ExecuteTestPattern);
} }
/// <inheritdoc />
public override string TransformerType => "Regex"; public override string TransformerType => "Regex";
/// <inheritdoc />
public override string DisplayName => "Regex Transform"; public override string DisplayName => "Regex Transform";
/// <inheritdoc />
public override string Icon => "󰑑"; // mdi-regex public override string Icon => "󰑑"; // mdi-regex
/// <inheritdoc />
public override string Summary => !string.IsNullOrEmpty(_columnName) public override string Summary => !string.IsNullOrEmpty(_columnName)
? $"{_columnName}: {(_isFindReplaceMode ? "Replace" : "Extract")}" ? $"{_columnName}: {(_isFindReplaceMode ? "Replace" : "Extract")}"
: "Configure..."; : "Configure...";
@@ -512,54 +580,81 @@ public class RegexTransformerViewModel : TransformerStepViewModelBase
[NonMatchBehavior.KeepOriginal, NonMatchBehavior.ReturnNull, NonMatchBehavior.ReturnEmpty]; [NonMatchBehavior.KeepOriginal, NonMatchBehavior.ReturnNull, NonMatchBehavior.ReturnEmpty];
// Test feature properties // Test feature properties
/// <summary>
/// Gets or sets the test input value for pattern testing.
/// </summary>
public string TestInput public string TestInput
{ {
get => _testInput; get => _testInput;
set => SetProperty(ref _testInput, value ?? string.Empty); set => SetProperty(ref _testInput, value ?? string.Empty);
} }
/// <summary>
/// Gets or sets the result value from pattern testing.
/// </summary>
public string TestResultValue public string TestResultValue
{ {
get => _testResultValue; get => _testResultValue;
set => SetProperty(ref _testResultValue, value); set => SetProperty(ref _testResultValue, value);
} }
/// <summary>
/// Gets or sets the label describing the test result (e.g., "Output" or "No Match").
/// </summary>
public string TestResultLabel public string TestResultLabel
{ {
get => _testResultLabel; get => _testResultLabel;
set => SetProperty(ref _testResultLabel, value); set => SetProperty(ref _testResultLabel, value);
} }
/// <summary>
/// Gets or sets the icon for the test result.
/// </summary>
public string TestResultIcon public string TestResultIcon
{ {
get => _testResultIcon; get => _testResultIcon;
set => SetProperty(ref _testResultIcon, value); set => SetProperty(ref _testResultIcon, value);
} }
/// <summary>
/// Gets or sets the background color for the test result display.
/// </summary>
public string TestResultBackground public string TestResultBackground
{ {
get => _testResultBackground; get => _testResultBackground;
set => SetProperty(ref _testResultBackground, value); set => SetProperty(ref _testResultBackground, value);
} }
/// <summary>
/// Gets or sets a value indicating whether a test result is available.
/// </summary>
public bool HasTestResult public bool HasTestResult
{ {
get => _hasTestResult; get => _hasTestResult;
set => SetProperty(ref _hasTestResult, value); set => SetProperty(ref _hasTestResult, value);
} }
/// <summary>
/// Gets or sets a value indicating whether a test error occurred.
/// </summary>
public bool HasTestError public bool HasTestError
{ {
get => _hasTestError; get => _hasTestError;
set => SetProperty(ref _hasTestError, value); set => SetProperty(ref _hasTestError, value);
} }
/// <summary>
/// Gets or sets the error message from test execution.
/// </summary>
public string TestErrorMessage public string TestErrorMessage
{ {
get => _testErrorMessage; get => _testErrorMessage;
set => SetProperty(ref _testErrorMessage, value); set => SetProperty(ref _testErrorMessage, value);
} }
/// <summary>
/// Gets the command to execute pattern testing.
/// </summary>
public ICommand TestPatternCommand { get; } public ICommand TestPatternCommand { get; }
private void ExecuteTestPattern() private void ExecuteTestPattern()
@@ -628,6 +723,7 @@ public class RegexTransformerViewModel : TransformerStepViewModelBase
TestErrorMessage = string.Empty; TestErrorMessage = string.Empty;
} }
/// <inheritdoc />
public override TransformElement ToModel() => new() public override TransformElement ToModel() => new()
{ {
TransformType = TransformerType, TransformType = TransformerType,
@@ -650,6 +746,9 @@ public static class TransformerFactory
/// <summary> /// <summary>
/// Creates a transformer view model from a TransformElement. /// Creates a transformer view model from a TransformElement.
/// </summary> /// </summary>
/// <param name="element">The transform element containing configuration.</param>
/// <param name="onChanged">Callback invoked when the view model changes.</param>
/// <returns>A transformer view model of the appropriate type, or null if the transformer type is unknown.</returns>
public static TransformerStepViewModelBase? Create(TransformElement element, Action onChanged) public static TransformerStepViewModelBase? Create(TransformElement element, Action onChanged)
{ {
return element.TransformType?.ToLowerInvariant() switch return element.TransformType?.ToLowerInvariant() switch
@@ -665,6 +764,9 @@ public static class TransformerFactory
/// <summary> /// <summary>
/// Creates a new transformer view model by type name. /// Creates a new transformer view model by type name.
/// </summary> /// </summary>
/// <param name="typeName">The type name of the transformer to create.</param>
/// <param name="onChanged">Callback invoked when the view model changes.</param>
/// <returns>A new transformer view model of the specified type, or null if the type name is unknown.</returns>
public static TransformerStepViewModelBase? CreateNew(string typeName, Action onChanged) public static TransformerStepViewModelBase? CreateNew(string typeName, Action onChanged)
{ {
return typeName?.ToLowerInvariant() switch return typeName?.ToLowerInvariant() switch
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Controls;
public partial class FlowArrow : UserControl public partial class FlowArrow : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="FlowArrow"/> class.
/// </summary>
public FlowArrow() public FlowArrow()
{ {
InitializeComponent(); InitializeComponent();
@@ -12,6 +12,9 @@ public partial class PipelineStepCard : UserControl
public static readonly StyledProperty<string> StepColorProperty = public static readonly StyledProperty<string> StepColorProperty =
AvaloniaProperty.Register<PipelineStepCard, string>(nameof(StepColor), "#3B82F6"); AvaloniaProperty.Register<PipelineStepCard, string>(nameof(StepColor), "#3B82F6");
/// <summary>
/// Initializes a new instance of the <see cref="PipelineStepCard"/> class.
/// </summary>
public PipelineStepCard() public PipelineStepCard()
{ {
InitializeComponent(); InitializeComponent();
@@ -5,11 +5,18 @@ namespace JdeScoping.ConfigManager.Views.Dialogs;
public partial class DiffPreviewDialog : Window public partial class DiffPreviewDialog : Window
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DiffPreviewDialog"/> class.
/// </summary>
public DiffPreviewDialog() public DiffPreviewDialog()
{ {
InitializeComponent(); InitializeComponent();
} }
/// <summary>
/// Initializes a new instance of the <see cref="DiffPreviewDialog"/> class with a view model.
/// </summary>
/// <param name="viewModel">The view model to bind to the dialog.</param>
public DiffPreviewDialog(DiffPreviewDialogViewModel viewModel) : this() public DiffPreviewDialog(DiffPreviewDialogViewModel viewModel) : this()
{ {
DataContext = viewModel; DataContext = viewModel;
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class ColumnDropEditorView : UserControl public partial class ColumnDropEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ColumnDropEditorView"/> class.
/// </summary>
public ColumnDropEditorView() public ColumnDropEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class ColumnRenameEditorView : UserControl public partial class ColumnRenameEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ColumnRenameEditorView"/> class.
/// </summary>
public ColumnRenameEditorView() public ColumnRenameEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class DestinationEditorView : UserControl public partial class DestinationEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DestinationEditorView"/> class.
/// </summary>
public DestinationEditorView() public DestinationEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class JdeDateEditorView : UserControl public partial class JdeDateEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="JdeDateEditorView"/> class.
/// </summary>
public JdeDateEditorView() public JdeDateEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class PostScriptEditorView : UserControl public partial class PostScriptEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="PostScriptEditorView"/> class.
/// </summary>
public PostScriptEditorView() public PostScriptEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class RegexEditorView : UserControl public partial class RegexEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="RegexEditorView"/> class.
/// </summary>
public RegexEditorView() public RegexEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class ScriptEditorView : UserControl public partial class ScriptEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ScriptEditorView"/> class.
/// </summary>
public ScriptEditorView() public ScriptEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Editors;
public partial class SourceEditorView : UserControl public partial class SourceEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="SourceEditorView"/> class.
/// </summary>
public SourceEditorView() public SourceEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class AuthFormView : UserControl public partial class AuthFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="AuthFormView"/> class.
/// </summary>
public AuthFormView() public AuthFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -279,71 +279,26 @@
</Border> </Border>
</StackPanel> </StackPanel>
<!-- Connection List --> <!-- Connection List -->
<Border BorderBrush="#2D3540" BorderThickness="1" CornerRadius="4"> <Border BorderBrush="#2D3540" BorderThickness="1" CornerRadius="4">
<StackPanel> <DataGrid ItemsSource="{Binding Connections}"
<!-- Header Row --> SelectedItem="{Binding SelectedConnection}"
<Grid Background="#151920" Height="32"> AutoGenerateColumns="False"
<Grid.ColumnDefinitions> IsReadOnly="True"
<ColumnDefinition Width="*"/> SelectionMode="Single"
<ColumnDefinition Width="100"/> GridLinesVisibility="Horizontal"
<ColumnDefinition Width="150"/> HeadersVisibility="Column"
</Grid.ColumnDefinitions> Background="#0D0F12"
<TextBlock Grid.Column="0" Text="Name" Foreground="#9BA8B8" RowBackground="#0D0F12"
FontSize="12" FontWeight="Medium" MaxHeight="220"
VerticalAlignment="Center" Margin="12,0"/> MinHeight="100">
<TextBlock Grid.Column="1" Text="Provider" Foreground="#9BA8B8" <DataGrid.Columns>
FontSize="12" FontWeight="Medium" <DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="Auto"/>
VerticalAlignment="Center" Margin="8,0"/> <DataGridTextColumn Header="Provider" Binding="{Binding ProviderDisplay}" Width="Auto"/>
<TextBlock Grid.Column="2" Text="Server" Foreground="#9BA8B8" <DataGridTextColumn Header="Server" Binding="{Binding ServerDisplay}" Width="*"/>
FontSize="12" FontWeight="Medium" </DataGrid.Columns>
VerticalAlignment="Center" Margin="8,0"/> </DataGrid>
</Grid> </Border>
<!-- List Items -->
<ListBox ItemsSource="{Binding Connections}"
SelectedItem="{Binding SelectedConnection}"
SelectionMode="Single"
Background="#0D0F12"
MaxHeight="180"
MinHeight="100">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="Background" Value="#2563EB"/>
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="#1E3A5F"/>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="36" Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"
Foreground="#E6EDF5" FontSize="13"
VerticalAlignment="Center" Margin="12,0"/>
<TextBlock Grid.Column="1" Text="{Binding ProviderDisplay}"
Foreground="#9BA8B8" FontSize="13"
VerticalAlignment="Center" Margin="8,0"/>
<TextBlock Grid.Column="2" Text="{Binding ServerDisplay}"
Foreground="#9BA8B8" FontSize="13"
VerticalAlignment="Center" Margin="8,0"/>
<Border Grid.ColumnSpan="3" BorderBrush="#2D3540"
BorderThickness="0,0,0,1" VerticalAlignment="Bottom"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Border>
<!-- Toolbar --> <!-- Toolbar -->
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel Orientation="Horizontal" Spacing="8">
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class ConnectionStringsFormView : UserControl public partial class ConnectionStringsFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionStringsFormView"/> class.
/// </summary>
public ConnectionStringsFormView() public ConnectionStringsFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class DataAccessFormView : UserControl public partial class DataAccessFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DataAccessFormView"/> class.
/// </summary>
public DataAccessFormView() public DataAccessFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class DataSyncFormView : UserControl public partial class DataSyncFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="DataSyncFormView"/> class.
/// </summary>
public DataSyncFormView() public DataSyncFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class ExcelExportFormView : UserControl public partial class ExcelExportFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ExcelExportFormView"/> class.
/// </summary>
public ExcelExportFormView() public ExcelExportFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class LdapFormView : UserControl public partial class LdapFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="LdapFormView"/> class.
/// </summary>
public LdapFormView() public LdapFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class PipelineEditorView : UserControl public partial class PipelineEditorView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="PipelineEditorView"/> class.
/// </summary>
public PipelineEditorView() public PipelineEditorView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class SearchFormView : UserControl public partial class SearchFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="SearchFormView"/> class.
/// </summary>
public SearchFormView() public SearchFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class SecretFormView : UserControl public partial class SecretFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="SecretFormView"/> class.
/// </summary>
public SecretFormView() public SecretFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -4,6 +4,9 @@ namespace JdeScoping.ConfigManager.Views.Forms;
public partial class SecureStoreInfoFormView : UserControl public partial class SecureStoreInfoFormView : UserControl
{ {
/// <summary>
/// Initializes a new instance of the <see cref="SecureStoreInfoFormView"/> class.
/// </summary>
public SecureStoreInfoFormView() public SecureStoreInfoFormView()
{ {
InitializeComponent(); InitializeComponent();
@@ -22,7 +22,7 @@ public class ConnectionStringsFormViewModelTests
} }
[Fact] [Fact]
public void Constructor_InitializesFromModel() public void Constructor_InitializesFromModel()
{ {
// Arrange // Arrange
var model = new ConnectionStringsSection var model = new ConnectionStringsSection
@@ -54,11 +54,67 @@ public class ConnectionStringsFormViewModelTests
sut.Connections[0].Server.ShouldBe("server1"); sut.Connections[0].Server.ShouldBe("server1");
sut.Connections[1].Name.ShouldBe("Connection2"); sut.Connections[1].Name.ShouldBe("Connection2");
sut.Connections[1].Provider.ShouldBe(ConnectionProvider.Oracle); sut.Connections[1].Provider.ShouldBe(ConnectionProvider.Oracle);
sut.Connections[1].Host.ShouldBe("oracle-host"); sut.Connections[1].Host.ShouldBe("oracle-host");
} }
[Fact] [Fact]
public void Constructor_ThrowsOnNullModel() public void Constructor_LoadsAndParsesSqlServerConnectionStringFromSecureStore()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
new ConnectionStringEntry { Name = "LotFinder" }
}
};
_secureStoreManager.IsStoreOpen.Returns(true);
_secureStoreManager.GetSecret("LotFinder")
.Returns("Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=pass;TrustServerCertificate=true");
// Act
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
// Assert
sut.Connections.Count.ShouldBe(1);
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
sut.Connections[0].Server.ShouldBe("localhost,1434");
sut.Connections[0].Database.ShouldBe("ScopingTool");
sut.Connections[0].UserId.ShouldBe("scopingapp");
sut.Connections[0].Password.ShouldBe("pass");
sut.Connections[0].TrustServerCertificate.ShouldBeTrue();
}
[Fact]
public void Constructor_LoadsAndParsesOracleConnectionStringFromSecureStore()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
new ConnectionStringEntry { Name = "CMS" }
}
};
_secureStoreManager.IsStoreOpen.Returns(true);
_secureStoreManager.GetSecret("CMS")
.Returns("HOST=ha-iman;Service Name=imanprd;Fetch Array Size=1280000;Port=1522;User ID=app_teamcenter;Password=pass;");
// Act
var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService);
// Assert
sut.Connections.Count.ShouldBe(1);
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.Oracle);
sut.Connections[0].Host.ShouldBe("ha-iman");
sut.Connections[0].ServiceName.ShouldBe("imanprd");
sut.Connections[0].Port.ShouldBe(1522);
sut.Connections[0].UserId.ShouldBe("app_teamcenter");
sut.Connections[0].Password.ShouldBe("pass");
}
[Fact]
public void Constructor_ThrowsOnNullModel()
{ {
// Act & Assert // Act & Assert
Should.Throw<ArgumentNullException>(() => Should.Throw<ArgumentNullException>(() =>