diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs index f64bbde..6c61f82 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringEntry.cs @@ -55,6 +55,11 @@ public class ConnectionStringEntry /// public string? ApplicationName { get; set; } + /// + /// Gets or sets the SQL Server port number. When null, no port is specified. + /// + public int? SqlServerPort { get; set; } + /// /// Gets or sets the Oracle server host name or address. /// @@ -94,7 +99,12 @@ public class ConnectionStringEntry var parts = new List(); if (!string.IsNullOrWhiteSpace(Server)) - parts.Add($"Server={Server}"); + { + var serverValue = SqlServerPort.HasValue && SqlServerPort.Value > 0 + ? $"{Server},{SqlServerPort.Value}" + : Server; + parts.Add($"Server={serverValue}"); + } if (!string.IsNullOrWhiteSpace(Database)) parts.Add($"Database={Database}"); if (!string.IsNullOrWhiteSpace(UserId)) diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSectionConverter.cs b/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSectionConverter.cs index 5c4f922..5d81755 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSectionConverter.cs +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Models/ConnectionStringsSectionConverter.cs @@ -62,11 +62,11 @@ public class ConnectionStringsSectionConverter : JsonConverter /// Parses a connection string and attempts to detect the provider type. /// - internal static ConnectionStringEntry ParseConnectionString(string name, string connectionString) - { - var entry = new ConnectionStringEntry - { - Name = name, - RawConnectionString = connectionString - }; - - // Try to detect provider and parse structured fields - var parts = ParseConnectionStringParts(connectionString); - - // Detect Oracle using HOST/Service Name/Port pattern (DDTek.Oracle style) - var hasHost = parts.TryGetValue("host", out var host); - var hasServiceName = parts.TryGetValue("service name", out var serviceName); - var hasPort = parts.TryGetValue("port", out var portText); - - 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); + internal static ConnectionStringEntry ParseConnectionString(string name, string connectionString) + { + var entry = new ConnectionStringEntry + { + Name = name, + RawConnectionString = connectionString + }; + + // Try to detect provider and parse structured fields + var parts = ParseConnectionStringParts(connectionString); + + // Detect Oracle using HOST/Service Name/Port pattern (DDTek.Oracle style) + var hasHost = parts.TryGetValue("host", out var host); + var hasServiceName = parts.TryGetValue("service name", out var serviceName); + var hasPort = parts.TryGetValue("port", out var portText); + + 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)) { @@ -212,27 +212,28 @@ public class ConnectionStringsSectionConverter : JsonConverter /// Parses a connection string into key-value pairs. diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs index c0d5d02..021c2ec 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs +++ b/NEW/src/Utils/JdeScoping.ConfigManager/ViewModels/Forms/ConnectionStringEntryViewModel.cs @@ -208,6 +208,24 @@ public class ConnectionStringEntryViewModel : ViewModelBase } } + /// + /// Gets or sets the SQL Server port number. + /// + public int? SqlServerPort + { + get => _model.SqlServerPort; + set + { + if (_model.SqlServerPort != value) + { + _model.SqlServerPort = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(GeneratedConnectionString)); + _onChanged(); + } + } + } + /// /// Gets or sets the Oracle host name. /// diff --git a/NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml b/NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml index 3c02fd5..fd34eeb 100644 --- a/NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml +++ b/NEW/src/Utils/JdeScoping.ConfigManager/Views/Forms/ConnectionStringsFormView.axaml @@ -10,8 +10,8 @@ - - + + - - + + + + + + + + + @@ -279,26 +291,26 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs index ef4329a..f38163e 100644 --- a/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringEntryTests.cs @@ -107,6 +107,7 @@ public class ConnectionStringEntryTests entry.Name.ShouldBe(string.Empty); entry.Provider.ShouldBe(ConnectionProvider.Generic); entry.Server.ShouldBeNull(); + entry.SqlServerPort.ShouldBeNull(); entry.Database.ShouldBeNull(); entry.UserId.ShouldBeNull(); entry.Password.ShouldBeNull(); @@ -119,4 +120,70 @@ public class ConnectionStringEntryTests entry.ServiceName.ShouldBeNull(); entry.RawConnectionString.ShouldBeNull(); } + + [Fact] + public void GenerateConnectionString_SqlServer_WithPort_IncludesPortInServer() + { + // Arrange + var entry = new ConnectionStringEntry + { + Provider = ConnectionProvider.SqlServer, + Server = "localhost", + SqlServerPort = 1434, + Database = "TestDb", + UserId = "sa", + Password = "secret123" + }; + + // Act + var result = entry.GenerateConnectionString(); + + // Assert + result.ShouldContain("Server=localhost,1434"); + result.ShouldContain("Database=TestDb"); + } + + [Fact] + public void GenerateConnectionString_SqlServer_WithoutPort_NoPortInServer() + { + // Arrange + var entry = new ConnectionStringEntry + { + Provider = ConnectionProvider.SqlServer, + Server = "localhost", + SqlServerPort = null, + Database = "TestDb", + UserId = "sa", + Password = "secret123" + }; + + // Act + var result = entry.GenerateConnectionString(); + + // Assert + result.ShouldContain("Server=localhost;"); + result.ShouldNotContain("Server=localhost,"); + } + + [Fact] + public void GenerateConnectionString_SqlServer_WithZeroPort_NoPortInServer() + { + // Arrange + var entry = new ConnectionStringEntry + { + Provider = ConnectionProvider.SqlServer, + Server = "localhost", + SqlServerPort = 0, + Database = "TestDb", + UserId = "sa", + Password = "secret123" + }; + + // Act + var result = entry.GenerateConnectionString(); + + // Assert + result.ShouldContain("Server=localhost;"); + result.ShouldNotContain("Server=localhost,"); + } } diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringsSectionConverterTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringsSectionConverterTests.cs index 5265a09..af669da 100644 --- a/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringsSectionConverterTests.cs +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/Models/ConnectionStringsSectionConverterTests.cs @@ -34,7 +34,8 @@ public class ConnectionStringsSectionConverterTests var lotFinder = section.Entries.First(e => e.Name == "LotFinder"); lotFinder.Provider.ShouldBe(ConnectionProvider.SqlServer); - lotFinder.Server.ShouldBe("localhost,1434"); + lotFinder.Server.ShouldBe("localhost,1434"); // Port embedded in server, not parsed separately + lotFinder.SqlServerPort.ShouldBeNull(); lotFinder.Database.ShouldBe("ScopingTool"); lotFinder.UserId.ShouldBe("sa"); @@ -195,4 +196,83 @@ public class ConnectionStringsSectionConverterTests secondary.Port.ShouldBe(1521); secondary.ServiceName.ShouldBe("ORCL"); } + + [Fact] + public void Deserialize_SqlServerWithEmbeddedPort_KeepsPortInServer() + { + // Arrange - connection string with port embedded in server name (legacy format) + var json = """ + { + "TestDb": "Server=localhost,1434;Database=TestDB;User Id=testuser;Password=testpass" + } + """; + + // Act + var section = JsonSerializer.Deserialize(json, JsonOptions); + + // Assert - port is NOT parsed out, stays embedded in server name + section.ShouldNotBeNull(); + section.Entries.Count.ShouldBe(1); + + var entry = section.Entries[0]; + entry.Name.ShouldBe("TestDb"); + entry.Provider.ShouldBe(ConnectionProvider.SqlServer); + entry.Server.ShouldBe("localhost,1434"); // Port stays in server name + entry.SqlServerPort.ShouldBeNull(); // Port control not populated from parsing + entry.Database.ShouldBe("TestDB"); + } + + [Fact] + public void Deserialize_SqlServerWithoutPort_ServerHasNoPort() + { + // Arrange + var json = """ + { + "TestDb": "Server=myserver;Database=TestDB;User Id=testuser;Password=testpass" + } + """; + + // Act + var section = JsonSerializer.Deserialize(json, JsonOptions); + + // Assert + section.ShouldNotBeNull(); + section.Entries.Count.ShouldBe(1); + + var entry = section.Entries[0]; + entry.Server.ShouldBe("myserver"); + entry.SqlServerPort.ShouldBeNull(); + } + + [Fact] + public void RoundTrip_SqlServerWithPort_EmbedsPortInServer() + { + // Arrange - entry with separate port control + var original = new ConnectionStringsSection + { + Entries = new List + { + new() + { + Name = "WithPort", + Provider = ConnectionProvider.SqlServer, + Server = "localhost", + SqlServerPort = 1434, + Database = "DB1", + UserId = "user1", + Password = "pass1" + } + } + }; + + // Act + var json = JsonSerializer.Serialize(original, JsonOptions); + var deserialized = JsonSerializer.Deserialize(json, JsonOptions); + + // Assert - after round trip, port is embedded in server name (not parsed back out) + deserialized.ShouldNotBeNull(); + var entry = deserialized.Entries.First(); + entry.Server.ShouldBe("localhost,1434"); // Port embedded in server after round trip + entry.SqlServerPort.ShouldBeNull(); // Port control not populated from parsing + } } diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs index 68dba00..e50e782 100644 --- a/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringEntryViewModelTests.cs @@ -14,6 +14,7 @@ public class ConnectionStringEntryViewModelTests Name = "TestConnection", Provider = ConnectionProvider.SqlServer, Server = "localhost", + SqlServerPort = 1434, Database = "TestDb", UserId = "testuser", Password = "testpass", @@ -30,6 +31,7 @@ public class ConnectionStringEntryViewModelTests sut.Name.ShouldBe("TestConnection"); sut.Provider.ShouldBe(ConnectionProvider.SqlServer); sut.Server.ShouldBe("localhost"); + sut.SqlServerPort.ShouldBe(1434); sut.Database.ShouldBe("TestDb"); sut.UserId.ShouldBe("testuser"); sut.Password.ShouldBe("testpass"); @@ -179,4 +181,39 @@ public class ConnectionStringEntryViewModelTests // Assert sut.ServerDisplay.ShouldBe("-"); } + + [Fact] + public void SqlServerPort_PropertyChange_UpdatesModel() + { + // Arrange + var model = new ConnectionStringEntry(); + var changedInvoked = false; + var sut = new ConnectionStringEntryViewModel(model, () => changedInvoked = true); + + // Act + sut.SqlServerPort = 1434; + + // Assert + model.SqlServerPort.ShouldBe(1434); + changedInvoked.ShouldBeTrue(); + } + + [Fact] + public void SqlServerPort_PropertyChange_UpdatesGeneratedConnectionString() + { + // Arrange + var model = new ConnectionStringEntry + { + Provider = ConnectionProvider.SqlServer, + Server = "localhost", + Database = "TestDb" + }; + var sut = new ConnectionStringEntryViewModel(model, () => { }); + + // Act + sut.SqlServerPort = 1434; + + // Assert + sut.GeneratedConnectionString.ShouldContain("Server=localhost,1434"); + } } diff --git a/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs index c8edfde..f56c287 100644 --- a/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs +++ b/NEW/tests/JdeScoping.ConfigManager.Tests/ViewModels/Forms/ConnectionStringsFormViewModelTests.cs @@ -75,10 +75,11 @@ public class ConnectionStringsFormViewModelTests // Act var sut = new ConnectionStringsFormViewModel(model, _secureStoreManager, () => { }, _dialogService, _connectionTestService); - // Assert + // 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");