feat: 初始化NewLife Studio项目,完成基础框架与数据管理模块
何炳宏 authored at 2026-05-26 12:09:09
8.64 KiB
NewLife.Studio
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);
    }
}