using System.Collections.ObjectModel;
using Moq;
using NewLife.Studio.Core.DTOs;
using NewLife.Studio.Data;
using NewLife.Studio.Modules.DataStudio.ViewModels;
using NewLife.Studio.Store;
using Xunit;
namespace NewLife.Studio.Modules.DataStudio.Tests;
public class ConnectionListViewModelTests
{
private readonly Mock<IStoreService> _storeServiceMock;
private readonly Mock<IDataProvider> _dataProviderMock;
private readonly ConnectionListViewModel _vm;
public ConnectionListViewModelTests()
{
_storeServiceMock = new Mock<IStoreService>();
_dataProviderMock = new Mock<IDataProvider>();
_vm = new ConnectionListViewModel(_storeServiceMock.Object, _dataProviderMock.Object);
}
[Fact]
public void InitialState_HasEmptyConnections()
{
Assert.NotNull(_vm.Connections);
Assert.Empty(_vm.Connections);
}
[Fact]
public void InitialState_SelectedConnection_IsNull()
{
Assert.Null(_vm.SelectedConnection);
}
[Fact]
public async Task LoadAsync_PopulatesConnections_FromStore()
{
var storedConnections = new List<ConnectionInfo>
{
new() { Id = "1", Name = "SQLite-Local", ConnectionString = "Data Source=:memory:", ProviderType = "sqlite" },
new() { Id = "2", Name = "Postgres-Prod", ConnectionString = "Host=localhost;...", ProviderType = "postgres" }
};
_storeServiceMock
.Setup(s => s.ListConnectionsAsync())
.ReturnsAsync(storedConnections);
await _vm.LoadAsync();
Assert.Equal(2, _vm.Connections.Count);
Assert.Equal("SQLite-Local", _vm.Connections[0].Name);
Assert.Equal("Postgres-Prod", _vm.Connections[1].Name);
}
[Fact]
public async Task LoadAsync_WithEmptyStore_ResultsInEmptyConnections()
{
_storeServiceMock
.Setup(s => s.ListConnectionsAsync())
.ReturnsAsync(new List<ConnectionInfo>());
await _vm.LoadAsync();
Assert.Empty(_vm.Connections);
}
[Fact]
public async Task AddConnectionCommand_AddsConnection_ToCollection()
{
Assert.Empty(_vm.Connections);
await _vm.AddConnectionCommand.ExecuteAsync(null);
Assert.Single(_vm.Connections);
var conn = _vm.Connections[0];
Assert.StartsWith("SQLite-", conn.Name);
Assert.Equal("Data Source=:memory:", conn.ConnectionString);
Assert.Equal("sqlite", conn.ProviderType);
_storeServiceMock.Verify(
s => s.SaveConnectionAsync(It.Is<ConnectionInfo>(c => c.ProviderType == "sqlite")),
Times.Once);
}
[Fact]
public async Task AddConnectionCommand_SetsSelectedConnection()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
Assert.NotNull(_vm.SelectedConnection);
Assert.Same(_vm.Connections[0], _vm.SelectedConnection);
}
[Fact]
public async Task AddConnectionCommand_MultipleConnections_SavesEach()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
await _vm.AddConnectionCommand.ExecuteAsync(null);
Assert.Equal(2, _vm.Connections.Count);
_storeServiceMock.Verify(
s => s.SaveConnectionAsync(It.IsAny<ConnectionInfo>()),
Times.Exactly(2));
}
[Fact]
public async Task DeleteConnectionCommand_WithSelectedConnection_RemovesIt()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
var conn = _vm.SelectedConnection;
Assert.NotNull(conn);
await _vm.DeleteConnectionCommand.ExecuteAsync(null);
Assert.Empty(_vm.Connections);
Assert.Null(_vm.SelectedConnection);
_storeServiceMock.Verify(
s => s.DeleteConnectionAsync(conn!.Id),
Times.Once);
}
[Fact]
public async Task DeleteConnectionCommand_WithNoSelection_DoesNothing()
{
_vm.SelectedConnection = null;
await _vm.DeleteConnectionCommand.ExecuteAsync(null);
Assert.Empty(_vm.Connections);
_storeServiceMock.Verify(
s => s.DeleteConnectionAsync(It.IsAny<string>()),
Times.Never);
}
[Fact]
public async Task EditConnectionCommand_WithSelectedConnection_SavesToStore()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
// Reset mock invocations to isolate the EditConnection call
_storeServiceMock.Invocations.Clear();
_vm.SelectedConnection!.Name = "Renamed";
await _vm.EditConnectionCommand.ExecuteAsync(null);
_storeServiceMock.Verify(
s => s.SaveConnectionAsync(It.Is<ConnectionInfo>(c => c.Name == "Renamed")),
Times.Once);
}
[Fact]
public async Task EditConnectionCommand_WithNoSelection_DoesNothing()
{
_vm.SelectedConnection = null;
await _vm.EditConnectionCommand.ExecuteAsync(null);
_storeServiceMock.Verify(
s => s.SaveConnectionAsync(It.IsAny<ConnectionInfo>()),
Times.Never);
}
[Fact]
public async Task TestConnectionCommand_WithNoSelection_DoesNothing()
{
_vm.SelectedConnection = null;
await _vm.TestConnectionCommand.ExecuteAsync(null);
_dataProviderMock.Verify(
p => p.TestConnectionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()),
Times.Never);
}
[Fact]
public async Task TestConnectionCommand_WithSelectedConnection_TestsConnection()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
_dataProviderMock
.Setup(p => p.TestConnectionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
await _vm.TestConnectionCommand.ExecuteAsync(null);
_dataProviderMock.Verify(
p => p.TestConnectionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()),
Times.Once);
}
[Fact]
public async Task TestConnectionCommand_FailedConnection_DoesNotThrow()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
_dataProviderMock
.Setup(p => p.TestConnectionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var exception = await Record.ExceptionAsync(
() => _vm.TestConnectionCommand.ExecuteAsync(null));
Assert.Null(exception);
}
[Fact]
public async Task OpenConnectionCommand_WithNoSelection_DoesNothing()
{
_vm.SelectedConnection = null;
await _vm.OpenConnectionCommand.ExecuteAsync(null);
_dataProviderMock.Verify(
p => p.OpenSessionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()),
Times.Never);
}
[Fact]
public async Task OpenConnectionCommand_FiresConnectionOpenedEvent()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
var mockSession = new Mock<IDbSession>();
_dataProviderMock
.Setup(p => p.OpenSessionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockSession.Object);
IDbSession? receivedSession = null;
_vm.ConnectionOpened += (_, session) => receivedSession = session;
await _vm.OpenConnectionCommand.ExecuteAsync(null);
Assert.NotNull(receivedSession);
Assert.Same(mockSession.Object, receivedSession);
}
[Fact]
public async Task OpenConnectionCommand_UpdatesLastUsedAt()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
var now = new DateTime(2025, 1, 15, 12, 0, 0);
_vm.SelectedConnection!.LastUsedAt = DateTime.MinValue;
var mockSession = new Mock<IDbSession>();
_dataProviderMock
.Setup(p => p.OpenSessionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(mockSession.Object);
await _vm.OpenConnectionCommand.ExecuteAsync(null);
Assert.NotEqual(DateTime.MinValue, _vm.SelectedConnection!.LastUsedAt);
}
[Fact]
public async Task OpenConnectionCommand_Exception_DoesNotThrow()
{
await _vm.AddConnectionCommand.ExecuteAsync(null);
_dataProviderMock
.Setup(p => p.OpenSessionAsync(It.IsAny<ConnectionInfo>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("Connection failed"));
var exception = await Record.ExceptionAsync(
() => _vm.OpenConnectionCommand.ExecuteAsync(null));
Assert.Null(exception);
}
[Fact]
public void SelectedConnection_CanBeSetExternally()
{
var conn = new ConnectionInfo { Id = "test", Name = "External" };
_vm.SelectedConnection = conn;
Assert.Same(conn, _vm.SelectedConnection);
}
}
|