feat(configmanager): add ConnectionStrings editor with test connection support
Adds a new ConnectionStrings section to ConfigManager allowing users to manage database connection strings with provider selection, connection testing, and visual feedback for connection state.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.Models;
|
||||
|
||||
public class ConnectionStringEntryTests
|
||||
{
|
||||
[Fact]
|
||||
public void GenerateConnectionString_SqlServer_ProducesCorrectFormat()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost\\SQLEXPRESS",
|
||||
Database = "TestDb",
|
||||
UserId = "sa",
|
||||
Password = "secret123",
|
||||
Encrypt = "True",
|
||||
TrustServerCertificate = true,
|
||||
ConnectionTimeout = 60,
|
||||
ApplicationName = "TestApp"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = entry.GenerateConnectionString();
|
||||
|
||||
// Assert
|
||||
result.ShouldContain("Server=localhost\\SQLEXPRESS");
|
||||
result.ShouldContain("Database=TestDb");
|
||||
result.ShouldContain("User Id=sa");
|
||||
result.ShouldContain("Password=secret123");
|
||||
result.ShouldContain("Encrypt=True");
|
||||
result.ShouldContain("TrustServerCertificate=True");
|
||||
result.ShouldContain("Connection Timeout=60");
|
||||
result.ShouldContain("Application Name=TestApp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateConnectionString_SqlServer_OmitsDefaultTimeout()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb",
|
||||
UserId = "user",
|
||||
Password = "pass",
|
||||
ConnectionTimeout = 30 // Default value
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = entry.GenerateConnectionString();
|
||||
|
||||
// Assert
|
||||
result.ShouldNotContain("Connection Timeout=");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateConnectionString_Oracle_ProducesEZConnectFormat()
|
||||
{
|
||||
// Arrange
|
||||
var entry = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Oracle,
|
||||
Host = "oracle-server.example.com",
|
||||
Port = 1522,
|
||||
ServiceName = "ORCL",
|
||||
UserId = "scott",
|
||||
Password = "tiger"
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = entry.GenerateConnectionString();
|
||||
|
||||
// Assert
|
||||
result.ShouldContain("Data Source=//oracle-server.example.com:1522/ORCL");
|
||||
result.ShouldContain("User Id=scott");
|
||||
result.ShouldContain("Password=tiger");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerateConnectionString_Generic_ReturnsRawString()
|
||||
{
|
||||
// Arrange
|
||||
var rawConnString = "Driver={ODBC Driver};Server=myserver;Database=mydb;";
|
||||
var entry = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Generic,
|
||||
RawConnectionString = rawConnString
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = entry.GenerateConnectionString();
|
||||
|
||||
// Assert
|
||||
result.ShouldBe(rawConnString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultValues_AreCorrect()
|
||||
{
|
||||
// Act
|
||||
var entry = new ConnectionStringEntry();
|
||||
|
||||
// Assert
|
||||
entry.Name.ShouldBe(string.Empty);
|
||||
entry.Provider.ShouldBe(ConnectionProvider.Generic);
|
||||
entry.Server.ShouldBeNull();
|
||||
entry.Database.ShouldBeNull();
|
||||
entry.UserId.ShouldBeNull();
|
||||
entry.Password.ShouldBeNull();
|
||||
entry.Encrypt.ShouldBe("True");
|
||||
entry.TrustServerCertificate.ShouldBeFalse();
|
||||
entry.ConnectionTimeout.ShouldBe(30);
|
||||
entry.ApplicationName.ShouldBeNull();
|
||||
entry.Host.ShouldBeNull();
|
||||
entry.Port.ShouldBe(1521);
|
||||
entry.ServiceName.ShouldBeNull();
|
||||
entry.RawConnectionString.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class ConnectionStringEntryViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Name = "TestConnection",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "localhost",
|
||||
Database = "TestDb",
|
||||
UserId = "testuser",
|
||||
Password = "testpass",
|
||||
Encrypt = "Strict",
|
||||
TrustServerCertificate = true,
|
||||
ConnectionTimeout = 45,
|
||||
ApplicationName = "TestApp"
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.Name.ShouldBe("TestConnection");
|
||||
sut.Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Server.ShouldBe("localhost");
|
||||
sut.Database.ShouldBe("TestDb");
|
||||
sut.UserId.ShouldBe("testuser");
|
||||
sut.Password.ShouldBe("testpass");
|
||||
sut.Encrypt.ShouldBe("Strict");
|
||||
sut.TrustServerCertificate.ShouldBeTrue();
|
||||
sut.ConnectionTimeout.ShouldBe(45);
|
||||
sut.ApplicationName.ShouldBe("TestApp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ConnectionStringEntryViewModel(null!, () => { }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new ConnectionStringEntryViewModel(model, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_UpdatesModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Act
|
||||
sut.Name = "UpdatedName";
|
||||
sut.Server = "newserver";
|
||||
sut.Database = "newdb";
|
||||
|
||||
// Assert
|
||||
model.Name.ShouldBe("UpdatedName");
|
||||
model.Server.ShouldBe("newserver");
|
||||
model.Database.ShouldBe("newdb");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PropertyChange_InvokesOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => changedInvoked = true);
|
||||
|
||||
// Act
|
||||
sut.Name = "NewName";
|
||||
|
||||
// Assert
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TogglePasswordVisibility_TogglesIsPasswordVisible()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry();
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert initial state
|
||||
sut.IsPasswordVisible.ShouldBeFalse();
|
||||
|
||||
// Act - first toggle
|
||||
sut.TogglePasswordVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsPasswordVisible.ShouldBeTrue();
|
||||
|
||||
// Act - second toggle
|
||||
sut.TogglePasswordVisibilityCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.IsPasswordVisible.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProviderDisplay_ReturnsCorrectString()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry { Provider = ConnectionProvider.SqlServer };
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("SqlServer");
|
||||
|
||||
// Act
|
||||
sut.Provider = ConnectionProvider.Oracle;
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("Oracle");
|
||||
|
||||
// Act
|
||||
sut.Provider = ConnectionProvider.Generic;
|
||||
|
||||
// Assert
|
||||
sut.ProviderDisplay.ShouldBe("Generic");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsServerForSqlServer()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "sql-server-host"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("sql-server-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsHostForOracle()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Oracle,
|
||||
Host = "oracle-host"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("oracle-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ServerDisplay_ReturnsDashForGeneric()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringEntry
|
||||
{
|
||||
Provider = ConnectionProvider.Generic,
|
||||
RawConnectionString = "some connection string"
|
||||
};
|
||||
var sut = new ConnectionStringEntryViewModel(model, () => { });
|
||||
|
||||
// Assert
|
||||
sut.ServerDisplay.ShouldBe("-");
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Tests.ViewModels.Forms;
|
||||
|
||||
public class ConnectionStringsFormViewModelTests
|
||||
{
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
|
||||
public ConnectionStringsFormViewModelTests()
|
||||
{
|
||||
_dialogService = Substitute.For<IDialogService>();
|
||||
_connectionTestService = Substitute.For<IConnectionTestService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_InitializesFromModel()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "Connection1",
|
||||
Provider = ConnectionProvider.SqlServer,
|
||||
Server = "server1"
|
||||
},
|
||||
new ConnectionStringEntry
|
||||
{
|
||||
Name = "Connection2",
|
||||
Provider = ConnectionProvider.Oracle,
|
||||
Host = "oracle-host"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(2);
|
||||
sut.Connections[0].Name.ShouldBe("Connection1");
|
||||
sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer);
|
||||
sut.Connections[0].Server.ShouldBe("server1");
|
||||
sut.Connections[1].Name.ShouldBe("Connection2");
|
||||
sut.Connections[1].Provider.ShouldBe(ConnectionProvider.Oracle);
|
||||
sut.Connections[1].Host.ShouldBe("oracle-host");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullModel()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(null!, () => { }, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOnChanged()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, null!, _dialogService, _connectionTestService));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullConnectionTestService()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() =>
|
||||
new ConnectionStringsFormViewModel(model, () => { }, _dialogService, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddConnection_CreatesNewEntryAndSelectsIt()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection();
|
||||
var changedInvoked = false;
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => changedInvoked = true, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.Connections.Count.ShouldBe(1);
|
||||
sut.Connections[0].Name.ShouldBe("NewConnection");
|
||||
sut.SelectedConnection.ShouldBe(sut.Connections[0]);
|
||||
model.Entries.Count.ShouldBe(1);
|
||||
changedInvoked.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasSelection_IsFalseWhenNothingSelected()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert - no selection by default
|
||||
sut.SelectedConnection.ShouldBeNull();
|
||||
sut.HasSelection.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasSelection_IsTrueWhenConnectionSelected()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" }
|
||||
}
|
||||
};
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Act
|
||||
sut.SelectedConnection = sut.Connections[0];
|
||||
|
||||
// Assert
|
||||
sut.HasSelection.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectionCount_ReflectsCollectionSize()
|
||||
{
|
||||
// Arrange
|
||||
var model = new ConnectionStringsSection
|
||||
{
|
||||
Entries = new List<ConnectionStringEntry>
|
||||
{
|
||||
new ConnectionStringEntry { Name = "Conn1" },
|
||||
new ConnectionStringEntry { Name = "Conn2" },
|
||||
new ConnectionStringEntry { Name = "Conn3" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var sut = new ConnectionStringsFormViewModel(model, () => { }, _dialogService, _connectionTestService);
|
||||
|
||||
// Assert
|
||||
sut.ConnectionCount.ShouldBe(3);
|
||||
|
||||
// Act - add another
|
||||
sut.AddConnectionCommand.Execute(null);
|
||||
|
||||
// Assert
|
||||
sut.ConnectionCount.ShouldBe(4);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public class MainWindowViewModelTests
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IRuntimeConfigValidationService _runtimeValidationService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
private readonly ILogger<MainWindowViewModel> _logger;
|
||||
|
||||
public MainWindowViewModelTests()
|
||||
@@ -32,6 +33,7 @@ public class MainWindowViewModelTests
|
||||
_secureStoreManager = Substitute.For<ISecureStoreManager>();
|
||||
_clipboardService = Substitute.For<IClipboardService>();
|
||||
_runtimeValidationService = Substitute.For<IRuntimeConfigValidationService>();
|
||||
_connectionTestService = Substitute.For<IConnectionTestService>();
|
||||
_logger = Substitute.For<ILogger<MainWindowViewModel>>();
|
||||
|
||||
_validationService.ValidateAppSettings(Arg.Any<ConfigModel>())
|
||||
@@ -284,7 +286,7 @@ public class MainWindowViewModelTests
|
||||
// Without a configured/open SecureStore, only Settings and Pipelines appear
|
||||
sut.TreeNodes.Count.ShouldBe(2); // Settings, Pipelines (no Secure Store when not configured)
|
||||
sut.TreeNodes[0].Name.ShouldBe("Settings");
|
||||
sut.TreeNodes[0].Children.Count.ShouldBe(6); // DataSync, DataAccess, Auth, Ldap, Search, ExcelExport
|
||||
sut.TreeNodes[0].Children.Count.ShouldBe(7); // ConnectionStrings, DataSync, DataAccess, Auth, Ldap, Search, ExcelExport
|
||||
sut.TreeNodes[1].Name.ShouldBe("Pipelines");
|
||||
}
|
||||
|
||||
@@ -382,6 +384,7 @@ public class MainWindowViewModelTests
|
||||
_secureStoreManager,
|
||||
_clipboardService,
|
||||
_runtimeValidationService,
|
||||
_connectionTestService,
|
||||
_logger);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user