Files
Joseph Doherty 1fc7792cd1 refactor(configmanager): rename UI project and split test projects
Rename ConfigManager to ConfigManager.Ui to match the Core/CLI/UI project
structure, and split the monolithic test project into Core.Tests,
Cli.Tests, and Ui.Tests to align with the source project organization.
2026-01-28 10:24:36 -05:00

665 lines
23 KiB
C#

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<ISecureStoreManager>();
_dialogService = Substitute.For<IDialogService>();
_connectionTestService = Substitute.For<IConnectionTestService>();
// 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<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, _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<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 - 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<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
Should.Throw<ArgumentNullException>(() =>
new ConnectionStringsFormViewModel(null!, _secureStoreManager, () => { }, _dialogService, _connectionTestService));
}
[Fact]
public void Constructor_ThrowsOnNullSecureStoreManager()
{
// Arrange
var model = new ConnectionStringsSection();
// Act & Assert
Should.Throw<ArgumentNullException>(() =>
new ConnectionStringsFormViewModel(model, null!, () => { }, _dialogService, _connectionTestService));
}
[Fact]
public void Constructor_ThrowsOnNullOnChanged()
{
// Arrange
var model = new ConnectionStringsSection();
// Act & Assert
Should.Throw<ArgumentNullException>(() =>
new ConnectionStringsFormViewModel(model, _secureStoreManager, null!, _dialogService, _connectionTestService));
}
[Fact]
public void Constructor_ThrowsOnNullConnectionTestService()
{
// Arrange
var model = new ConnectionStringsSection();
// Act & Assert
Should.Throw<ArgumentNullException>(() =>
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<ConnectionStringEntry>
{
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<ConnectionStringEntry>
{
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<ConnectionStringEntry>
{
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<ConnectionStringEntry>
{
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<ConnectionStringEntry>
{
new ConnectionStringEntry { Name = "ToDelete" }
}
};
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
.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<ConnectionStringEntry>
{
new ConnectionStringEntry { Name = "ToKeep" }
}
};
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
.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<ConnectionStringEntry>
{
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<string>(s => s.Contains("empty connection string")));
}
[Fact]
public async Task ValidateConnectionCommand_WithValidConnectionString_ShowsSuccess()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
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<string>(s => s.Contains("valid connection string")));
}
[Fact]
public async Task TestConnectionCommand_WhenSuccessful_ShowsSuccessMessage()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
new ConnectionStringEntry
{
Name = "TestConn",
Provider = ConnectionProvider.SqlServer,
Server = "localhost",
Database = "TestDb"
}
}
};
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
.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<string>(s => s.Contains("Successfully connected")));
}
[Fact]
public async Task TestConnectionCommand_WhenFailed_ShowsErrorMessage()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
new ConnectionStringEntry
{
Name = "FailConn",
Provider = ConnectionProvider.SqlServer,
Server = "badserver",
Database = "TestDb"
}
}
};
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
.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<string>(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<ConnectionStringEntry>
{
new ConnectionStringEntry
{
Name = "TestConn",
Provider = ConnectionProvider.SqlServer,
Server = "localhost",
Database = "TestDb"
}
}
};
var taskCompletionSource = new TaskCompletionSource<ConnectionTestResult>();
_connectionTestService.TestConnectionAsync(Arg.Any<string>(), Arg.Any<ConnectionProvider>(), Arg.Any<CancellationToken>())
.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<ConnectionStringEntry>
{
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<ConnectionStringEntry>
{
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<string>());
}
[Fact]
public async Task DeleteConnectionCommand_RemovesFromSecureStore()
{
// Arrange
var model = new ConnectionStringsSection
{
Entries = new List<ConnectionStringEntry>
{
new ConnectionStringEntry { Name = "ToDelete" }
}
};
_secureStoreManager.IsStoreOpen.Returns(true);
_dialogService.ShowConfirmationAsync(Arg.Any<string>(), Arg.Any<string>())
.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<ConnectionStringEntry>
{
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<string>(s => s.Contains("connection string is empty")));
await _connectionTestService.DidNotReceive().TestConnectionAsync(
Arg.Any<string>(),
Arg.Any<ConnectionProvider>(),
Arg.Any<CancellationToken>());
}
[Fact]
public void AvailableProviders_ContainsAllProviders()
{
// Assert
ConnectionStringsFormViewModel.AvailableProviders.Count.ShouldBe(
Enum.GetValues<ConnectionProvider>().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");
}
}