using JdeScoping.ConfigManager.Core.Models; using JdeScoping.ConfigManager.Core.Services; using JdeScoping.ConfigManager.Core.Services.SecureStore; using JdeScoping.ConfigManager.Ui.Services; using JdeScoping.ConfigManager.Ui.ViewModels.Forms; namespace JdeScoping.ConfigManager.Ui.Tests.ViewModels.Forms; public class ConnectionStringsFormViewModelTests { private readonly ISecureStoreManager _secureStoreManager; private readonly IDialogService _dialogService; private readonly IConnectionTestService _connectionTestService; public ConnectionStringsFormViewModelTests() { _secureStoreManager = Substitute.For(); _dialogService = Substitute.For(); _connectionTestService = Substitute.For(); // Setup default behavior - SecureStore is not open by default in tests _secureStoreManager.IsStoreOpen.Returns(false); } [Fact] public void Constructor_InitializesFromModel() { // Arrange var model = new ConnectionStringsSection { Entries = new List { 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, _secureStoreManager, () => { }, _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_LoadsAndParsesSqlServerConnectionStringFromSecureStore() { // Arrange var model = new ConnectionStringsSection { Entries = new List { 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 - port stays embedded in server name, not parsed into SqlServerPort sut.Connections.Count.ShouldBe(1); sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer); sut.Connections[0].Server.ShouldBe("localhost,1434"); sut.Connections[0].SqlServerPort.ShouldBeNull(); 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 { 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 Should.Throw(() => new ConnectionStringsFormViewModel(null!, _secureStoreManager, () => { }, _dialogService, _connectionTestService)); } [Fact] public void Constructor_ThrowsOnNullSecureStoreManager() { // Arrange var model = new ConnectionStringsSection(); // Act & Assert Should.Throw(() => new ConnectionStringsFormViewModel(model, null!, () => { }, _dialogService, _connectionTestService)); } [Fact] public void Constructor_ThrowsOnNullOnChanged() { // Arrange var model = new ConnectionStringsSection(); // Act & Assert Should.Throw(() => new ConnectionStringsFormViewModel(model, _secureStoreManager, null!, _dialogService, _connectionTestService)); } [Fact] public void Constructor_ThrowsOnNullConnectionTestService() { // Arrange var model = new ConnectionStringsSection(); // Act & Assert Should.Throw(() => new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, null!)); } [Fact] public void AddConnection_CreatesNewEntryAndSelectsIt() { // Arrange var model = new ConnectionStringsSection(); var changedInvoked = false; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => 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 { new ConnectionStringEntry { Name = "Conn1" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _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 { new ConnectionStringEntry { Name = "Conn1" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _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 { new ConnectionStringEntry { Name = "Conn1" }, new ConnectionStringEntry { Name = "Conn2" }, new ConnectionStringEntry { Name = "Conn3" } } }; // Act var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); // Assert sut.ConnectionCount.ShouldBe(3); // Act - add another sut.AddConnectionCommand.Execute(null); // Assert sut.ConnectionCount.ShouldBe(4); } [Fact] public void Constructor_InitializesConnectionsFromModel() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "TestConnection", Provider = ConnectionProvider.SqlServer, Server = "localhost", Database = "TestDb", UserId = "sa", Password = "secret" } } }; // Act var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); // Assert sut.Connections.Count.ShouldBe(1); sut.Connections[0].Name.ShouldBe("TestConnection"); sut.Connections[0].Provider.ShouldBe(ConnectionProvider.SqlServer); sut.Connections[0].Server.ShouldBe("localhost"); sut.Connections[0].Database.ShouldBe("TestDb"); } [Fact] public void AddConnectionCommand_AddsNewConnection() { // Arrange var model = new ConnectionStringsSection(); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); var initialCount = sut.Connections.Count; // Act sut.AddConnectionCommand.Execute(null); // Assert sut.Connections.Count.ShouldBe(initialCount + 1); sut.Connections.Last().Name.ShouldBe("NewConnection"); sut.Connections.Last().Provider.ShouldBe(ConnectionProvider.Generic); } [Fact] public async Task DeleteConnectionCommand_WhenConfirmed_RemovesConnection() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "ToDelete" } } }; _dialogService.ShowConfirmationAsync(Arg.Any(), Arg.Any()) .Returns(true); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.DeleteConnectionCommand.Execute(null); await Task.Delay(100); // Assert sut.Connections.Count.ShouldBe(0); model.Entries.Count.ShouldBe(0); } [Fact] public async Task DeleteConnectionCommand_WhenCancelled_KeepsConnection() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "ToKeep" } } }; _dialogService.ShowConfirmationAsync(Arg.Any(), Arg.Any()) .Returns(false); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.DeleteConnectionCommand.Execute(null); await Task.Delay(100); // Assert sut.Connections.Count.ShouldBe(1); sut.Connections[0].Name.ShouldBe("ToKeep"); } [Fact] public async Task ValidateConnectionCommand_WithEmptyConnectionString_ShowsError() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "EmptyConnection", Provider = ConnectionProvider.Generic, RawConnectionString = "" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.ValidateConnectionCommand.Execute(null); await Task.Delay(100); // Assert await _dialogService.Received().ShowMessageAsync( "Validation Failed", Arg.Is(s => s.Contains("empty connection string"))); } [Fact] public async Task ValidateConnectionCommand_WithValidConnectionString_ShowsSuccess() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "ValidConnection", Provider = ConnectionProvider.SqlServer, Server = "localhost", Database = "TestDb" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.ValidateConnectionCommand.Execute(null); await Task.Delay(100); // Assert await _dialogService.Received().ShowMessageAsync( "Validation Passed", Arg.Is(s => s.Contains("valid connection string"))); } [Fact] public async Task TestConnectionCommand_WhenSuccessful_ShowsSuccessMessage() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "TestConn", Provider = ConnectionProvider.SqlServer, Server = "localhost", Database = "TestDb" } } }; _connectionTestService.TestConnectionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new ConnectionTestResult { Success = true, Duration = TimeSpan.FromMilliseconds(50) }); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.TestConnectionCommand.Execute(null); await Task.Delay(150); // Assert await _dialogService.Received().ShowMessageAsync( "Connection Successful", Arg.Is(s => s.Contains("Successfully connected"))); } [Fact] public async Task TestConnectionCommand_WhenFailed_ShowsErrorMessage() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "FailConn", Provider = ConnectionProvider.SqlServer, Server = "badserver", Database = "TestDb" } } }; _connectionTestService.TestConnectionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(new ConnectionTestResult { Success = false, Message = "Connection refused" }); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.TestConnectionCommand.Execute(null); await Task.Delay(150); // Assert await _dialogService.Received().ShowMessageAsync( "Connection Failed", Arg.Is(s => s.Contains("Failed to connect") && s.Contains("Connection refused"))); } [Fact] public async Task TestConnectionCommand_SetsIsTesting_DuringExecution() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "TestConn", Provider = ConnectionProvider.SqlServer, Server = "localhost", Database = "TestDb" } } }; var taskCompletionSource = new TaskCompletionSource(); _connectionTestService.TestConnectionAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(taskCompletionSource.Task); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act - Start the command sut.TestConnectionCommand.Execute(null); await Task.Delay(50); // Assert - IsTesting should be true during execution sut.IsTesting.ShouldBeTrue(); // Complete the task taskCompletionSource.SetResult(new ConnectionTestResult { Success = true }); await Task.Delay(100); // Assert - IsTesting should be false after completion sut.IsTesting.ShouldBeFalse(); } [Fact] public void SelectedConnection_WhenChanged_RaisesHasSelectionPropertyChanged() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "Conn1" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); var hasSelectionChangedRaised = false; sut.PropertyChanged += (s, e) => { if (e.PropertyName == nameof(ConnectionStringsFormViewModel.HasSelection)) hasSelectionChangedRaised = true; }; // Act sut.SelectedConnection = sut.Connections[0]; // Assert hasSelectionChangedRaised.ShouldBeTrue(); } [Fact] public void OnEntryChanged_SavesValueToSecureStore() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "TestConn", Provider = ConnectionProvider.SqlServer, Server = "localhost", Database = "TestDb" } } }; _secureStoreManager.IsStoreOpen.Returns(true); var onChangedCalled = false; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => onChangedCalled = true, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act - Change a property on the selected connection sut.SelectedConnection.Database = "NewDatabase"; // Assert onChangedCalled.ShouldBeTrue(); _secureStoreManager.Received().SetSecret("TestConn", Arg.Any()); } [Fact] public async Task DeleteConnectionCommand_RemovesFromSecureStore() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "ToDelete" } } }; _secureStoreManager.IsStoreOpen.Returns(true); _dialogService.ShowConfirmationAsync(Arg.Any(), Arg.Any()) .Returns(true); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.DeleteConnectionCommand.Execute(null); await Task.Delay(100); // Assert _secureStoreManager.Received().RemoveSecret("ToDelete"); } [Fact] public void AddConnectionCommand_CreatesSecureStoreEntry() { // Arrange var model = new ConnectionStringsSection(); _secureStoreManager.IsStoreOpen.Returns(true); var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); // Act sut.AddConnectionCommand.Execute(null); // Assert _secureStoreManager.Received().SetSecret("NewConnection", string.Empty); } [Fact] public async Task TestConnectionCommand_WithEmptyConnectionString_ShowsMessage() { // Arrange var model = new ConnectionStringsSection { Entries = new List { new ConnectionStringEntry { Name = "EmptyConn", Provider = ConnectionProvider.Generic, RawConnectionString = "" } } }; var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); sut.SelectedConnection = sut.Connections[0]; // Act sut.TestConnectionCommand.Execute(null); await Task.Delay(100); // Assert await _dialogService.Received().ShowMessageAsync( "Test Connection", Arg.Is(s => s.Contains("connection string is empty"))); await _connectionTestService.DidNotReceive().TestConnectionAsync( Arg.Any(), Arg.Any(), Arg.Any()); } [Fact] public void AvailableProviders_ContainsAllProviders() { // Assert ConnectionStringsFormViewModel.AvailableProviders.Count.ShouldBe( Enum.GetValues().Length); ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.SqlServer); ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.Oracle); ConnectionStringsFormViewModel.AvailableProviders.ShouldContain(ConnectionProvider.Generic); } [Fact] public void EncryptOptions_ContainsExpectedValues() { // Assert ConnectionStringsFormViewModel.EncryptOptions.Count.ShouldBe(3); ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("True"); ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("False"); ConnectionStringsFormViewModel.EncryptOptions.ShouldContain("Strict"); } }