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");